我正在尝试在我的一个组件中执行一些测试。我正在使用一个通过管道传输到 ActivatedRoute 的 paramMap 的可观察对象,以便从 URL 获取 guid,并使用 switchMap 将响应中的值分配给组件变量。我的观点是使用 observable 在屏幕上显示数据,当我通过 cli 提供应用程序时,一切似乎都工作正常。
一旦我尝试对组件执行测试,Karma 的浏览器选项卡中就会显示 ExpressionChangedAfterItHasBeenCheckedError。
我相信导致错误的原因是我正在更改 isBranded 变量的值,但我需要它在视图中显示正确的徽标。
我的组件代码:
@Component({
selector: 'app-digital-payment',
templateUrl: './digital-payment.component.html',
styleUrls: ['./digital-payment.component.scss']
})
export class DigitalPaymentComponent implements OnInit {
cdnUrl = environment.cdnUrl;
isBranded = false;
payment$: any;
constructor(private service: DigitalPaymentService, private activeRoute: ActivatedRoute) {
}
ngOnInit() {
this.payment$ = this.activeRoute.paramMap.pipe(switchMap((params: any) => {
let guid = params.get('guid');
return this.service.checkPaymentStatus(guid).pipe(map((data: any) => {
this.isBranded = data.isBranded;
return data;
}));
}));
}
}
我的看法:
<header>
<a class="logo">
<img src="{{ cdnUrl }}/Image/not-branded-logo.svg" alt="Not Branded Logo" />
</a>
<a class="logo pull-right text-right" *ngIf="isBranded">
<img src="{{ cdnUrl }}/Image/branded-logo.svg" alt="Branded Logo" />
</a>
</header>
<section>
<div class="payment-status {{ payment.class }}" *ngIf="payment$ | async as payment; else loading">
<mat-icon *ngIf="payment.icon">{{ payment.icon }}</mat-icon>
<h1>{{ payment.heading }}</h1>
<p>{{ payment.text }}</p>
</div>
<ng-template #loading>
<h1 class="loading-heading">Loading</h1>
<p>Please wait until your request has been executed.</p>
</ng-template>
</section>
我的服务:
@Injectable({
providedIn: 'root'
})
export class DigitalPaymentService {
private apiUrl: string = 'digitalpaymentexternal/';
private statusBinding = {
Success: {
class: 'success',
icon: 'check_circle_outline',
heading: 'Successful',
text: 'Your payment plan has been confirmed.'
},
Failed: {
class: 'fail',
icon: 'highlight_off',
heading: 'Transaction failed',
text: 'Please try a different payment method.'
},
Invalid: {
class: 'fail',
icon: 'error_outline',
heading: 'Incorrect link',
text: 'This link is invalid or it has been expired.'
}
};
constructor(private requester: RequesterService) { }
checkPaymentStatus(guid): Observable<any> {
return this.requester.post<void>(this.apiUrl + 'CheckPaymentStatus', guid).pipe(map((data: any) => {
return { ...this.statusBinding[data.status], isBranded: data.isBranded };
}));
}
}
还有我的组件测试代码:
describe('DigitalPaymentComponent', () => {
let component: DigitalPaymentComponent;
let fixture: ComponentFixture<DigitalPaymentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DigitalPaymentComponent],
providers: [
{
provide: DigitalPaymentService,
useValue: {
checkPaymentStatus: guid => of({ isBranded: true })
}
},
{
provide: ActivatedRoute,
useValue: {
paramMap: of(convertToParamMap({ guid: '00000000-0000-0000-0000-000000000000' }))
},
},
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DigitalPaymentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
可能的解决方案是在
this.cdr.detectChagnes();
或 ngOnInit()
内添加 ngAfterViewInit()
。 cdr
指的是ChangeDetectorRef
。
constructor(..., private cdr: ChangeDetectionRef) {}
ngOnInit() {
...
// for karma test to avoid ExpressionChangedAfterItHasBeenCheckedError error issue
this.cdr.detectChagnes();
}