Testing out some functionality in one of my components has led me to face an issue. I have set up an observable that is connected to the paramMap of the ActivatedRoute to retrieve a guid from the URL. This data is then processed using switchMap and assigned to a component variable. The information fetched is displayed on the view, and everything appears to be functioning correctly when running the app via the CLI.
However, when attempting to run tests for the component, I encounter the ExpressionChangedAfterItHasBeenCheckedError in Karma's browser tab.
The error seems to stem from the fact that I am modifying the value of the isBranded variable, which is essential for displaying the appropriate logo in the view.
https://i.sstatic.net/cqk4p.png
This is the code snippet for my component:
@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;
}));
}));
}
}
Here is how the view is structured:
<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>
Now, let's take a look at the Service:
@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 };
}));
}
}
Lastly, here is the test code for the component:
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();
});
});