0

I'm trying to unit test a small part of the Modal component, but I'm having hard times working with observables. My main problems are http requests that are not invoked one after another when I run code in Karma and Jasmine (I guess Angular TestBed is involved too).

What happens in real browser for the application is pretty simple: Click on button to show modal, fetch data (2 requests, one after another) and visualize it to the user. Works as expected, but as soon as I try to test it, the second http request is not sent.

Here's part of the code I wrote with comments:

export class CreateOrEditProjectModalComponent {
    private subs = new Subscription();
    private plan: Plan;

    constructor(private httpService: HttpService, private formService: FormService) {}

    show(id: string) {
        this.getModalData$(id).subscribe({
            complete: () => {
                // some action here
            }
        })
    }

    private getModalData$(projectId: string): Observable<void> {
        return this.getProject$(projectId).pipe(
            switchMap(({ project }) => {
                console.log(project); <- all good, received mocked project;

                this.formService.assignFormFields(project);

                console.log(this.formSerice.nameControl.value); <- all good, has value from mocked project;

                return this.getPlan$(project.planId);
            }),
            switchMap((plan) => {
                console.log(plan); <- problem, not reaching here;

                this.plan = plan;
                return EMPTY;
            })
        );
    }

    private getProject$(id: string): Observable<Project> {
        return this.httpService.getProject(id);
    }

    private getPlan$(id: string): Observable<Plan> {
        return this.httpService.getPlan(id);
    }
}
it('should get project and plan data after showing the modal', () => {
    component.show('123456');

    const projectReq = httpTestingController.matchOne('/api/project/123456');
    projectReq.flush(mockedProject); <- all good until this point;

    expect(component.formService.nameControl.value).toBe(mockedProject.name); <- This one is not working too. I expect that after flushing, it will go through the code specified in component.ts file (will assign project values to formService reactive form controls)
    
    const planReq = httpTestingController.matchOne('/api/plan/1');

    // Test runner fails here: Expected one matching request for criteria "Match URL: app/plan/1", found none.
    planReq.flush(mockedPlan); 
} 

I have no clue how I can reach to the second observable or why the values in formService are not available after first flush()

VRa3
  • 21
  • 3

1 Answers1

0

The problem you are facing has to do with the asynchrony of the observable tests. To ensure the correct order of testing, you should use the fakeAsync function and the tick from the "@angular/core/testing" package.

Apply fakeAsync to the test:

it('should get project and plan data after showing the modal', fakeAsync(() => {
  ...
}));

Put a tick() after the first flush():

projectReq.flush(mockedProject);
tick();

Thanks to these changes, tests will be performed synchronously and HTTP requests will be processed in the correct order. Also, make sure fakeAsync tests are terminated with discardPeriodicTasks() to release uncompleted asynchronous tasks.

SparrowVic
  • 274
  • 1
  • 7