Home > database >  Getting ExpressionChangedAfterItHasBeenCheckedError in Karma testing
Getting ExpressionChangedAfterItHasBeenCheckedError in Karma testing

Time:06-14

I'm trying to perform some tests in one of my components. I am using an observable which is piped to the paramMap of the ActivatedRoute in order to get a guid from the URL and using a switchMap where I assign a value from the response into a component variable. My view is using the observable to show the data on screen and all seems to be working fine when I serve the app via the cli.

Once I try to perform the tests for my component the ExpressionChangedAfterItHasBeenCheckedError shows in the Karma's browser tab.

I believe the error is caused because I am changing the value of the isBranded variable, but I need it to show the proper logo in the view.

Karma Error

My component code:

@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;
            }));
        }));
    }
}

My view:

<header>
    <a >
        <img src="{{ cdnUrl }}/Image/not-branded-logo.svg" alt="Not Branded Logo" />
    </a>
    <a  *ngIf="isBranded">
        <img src="{{ cdnUrl }}/Image/branded-logo.svg" alt="Branded Logo" />
    </a>
</header>

<section>
    <div  *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 >Loading</h1>
        <p>Please wait until your request has been executed.</p>
    </ng-template>
</section>

My 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 };
        }));
    }
}

And my component test code:

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();
    });
});

CodePudding user response:

Probable solution is to add this.cdr.detectChagnes(); inside ngOnInit() or ngAfterViewInit(). cdr refers to ChangeDetectionRef.

constructor(..., private cdr: ChangeDetectionRef) {}

ngOnInit() {
    ...
    // for karma test to avoid ExpressionChangedAfterItHasBeenCheckedError error issue
    this.cdr.detectChagnes();
}
  • Related