Encountering the ExpressionChangedAfterItHasBeenCheckedError error during Karma testing

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.

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

Answer №1

One potential solution is to include this.cdr.detectChanges(); within either ngOnInit() or ngAfterViewInit(). The variable cdr stands for ChangeDetectorRef.

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

ngOnInit() {
    ...
    // To prevent the ExpressionChangedAfterItHasBeenCheckedError issue during karma testing
    this.cdr.detectChanges();
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Revamping every Angular component

Looking for a shortcut to upgrade all my Angular components at once. When checking my Angular version with ng v globally in VS Code terminal, results differ between overall and project directory settings: https://i.stack.imgur.com/2iOJx.png https://i.st ...

select items using a dropdown menu in an Angular application

Let me describe a scenario where I am facing an issue. I have created an HTML table with certain elements and a drop-down list Click here for image illustration When the user selects in, only records with type in should be displayed Another image refere ...

What is the abbreviation for indicating a return type as nullable?

Is there a way to use shorthand for nullable return types in TypeScript similar to using "name?: type" for parameters? function veryUsefulFunction(val?: string /* this is OK */) // but not this or other things I've tried. // is there a way a ...

Establishing the value of "document.cookie"

Encountering issues while trying to set a cookie using different methods: Method 1: document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/"; This method only sets the value up to "name=value" wh ...

What could be the cause of this malfunction in the Angular Service?

After creating an Angular app with a controller, I noticed that while I can successfully interact with the controller using Postman (as shown in the screenshot below), I faced issues with displaying data at the frontend. I implemented a new component alon ...

Is it possible to effortlessly associate a personalized string with an identifier within an HTML element utilizing Angular2?

Check out this cool plunker import {Component} from 'angular2/core' @Component({ selector: 'my-app', template: ` <div *ngFor="#option of myHashMap"> <input type="radio" name="myRadio" id="{{generateId(option[& ...

Trying to determine the specific key of an object based on its value in TypeScript?

Looking to create a function that can retrieve the key for a given value. type Items<T> = Exclude<{ [P in keyof T]: [P, T[P]]; }[keyof T], undefined>[]; export const getKeyName = <T extends Record<PropertyKey, unknown>, V>( col ...

Tips for customizing the `src/app/layout.tsx` file in Next.js 13

I am looking to customize the layout for my /admin route and its child routes (including /admin/*). How can I modify the main layout only for the /admin/* routes? For example, I want the / and /profile routes to use the layout defined in src/app/layout.ts ...

ServiceWorker has responded with a 503 OK status code

Recently, I implemented @angular/service-worker to create a SW for my angular4 web application. However, I encountered an issue after updating the ngsw-manifest.json file to handle dynamic requests from the server - now, whenever I go offline (after initia ...

"Revolutionizing the way we navigate: Angular's innovative

Presently, my focus is on incorporating route transitions into my project. I've employed a component that appears on click and triggers the corresponding service function: routeTransition(destination) { if (this.router.url !== destination) { t ...

Can Selenium be used for testing driven by Excel spreadsheets?

With Sahi, I was able to input the required functions into a spreadsheet and have them executed. It allowed for various combinations which led to different outcomes. Is it possible to achieve something similar using selenium? I am interested in selecting a ...

Form for creating and updating users with a variety of input options, powered by Angular 2+

As I work on creating a form, I encounter the need to distinguish between two scenarios. If the user selects 'create a user', the password inputs should be displayed. On the other hand, if the user chooses to edit a user, then the password inputs ...

Pending activation of the Timer Fired event

Below is some code I have for implementing swipe gesture functionality: this.topSlide = this.elementRef.nativeElement.querySelector('.product_rate_slide'); if (this.topSlide) { this.topSlide.addEventListener('touchstart', this.hand ...

NGXS: Issue with passing objects to "setState" occurs when any of the patched inner fields is nullable or undefined

In my Angular components, I have created a class that is responsible for storing the state: export class MyStateModel { subState1: SubStateModel1; substate2: SubStateModel2; } This class has simple subclasses: export class SubStateModel1 { someField ...

Weighing the importance of a multiple-choice quiz

I am faced with the challenge of assessing 25 multiple choice questions, each offering 4 answer choices worth varying points. Additionally, I have 2 free response questions that I wish to score based on the time taken to answer them. My proposed scoring ru ...

What is the best way to duplicate a Typescript class object while making changes to specific properties?

I have a Typescript cat class: class Kitty { constructor( public name: string, public age: number, public color: string ) {} } const mittens = new Kitty('Mittens', 5, 'gray') Now I want to create a clone of the inst ...

Using MSAL for authentication in combination with NGRX for state management

I am currently using MSAL login, which automatically redirects to the Microsoft login page. However, when I set the cache location to 'none' instead of 'localStorage', it is not redirecting. I prefer not to store the token in local sto ...

Unable to trigger click or keyup event

After successfully implementing *ngFor to display my data, I encountered an issue where nothing happens when I try to trigger an event upon a change. Below is the snippet of my HTML code: <ion-content padding class="home"> {{ searchString ...

Presenting a data table in Angular

I'm new to using Angular and need help creating a table to organize my data, which is being fetched from a JSON file on the server. Here's the content of data.component.html: <div class="container"> <h1>Search history</h1> ...

Find any consecutive lowercase or uppercase letter and include one more

I have a task in Javascript that I need help with. The goal is to insert a special character between a lowercase and uppercase letter when they are matched together. For example: myHouse => my_House aRandomString => a_Random_String And so on... T ...