Menu Home

Angular unit testing: Error: Expected one matching request for criteria … found none

I ran into this problem whilst writing a unit test using the Jasmine framework and this is how I solved it.

The code under test

getTrainInformation(url: string): Observable<any> {
        return this.http.get(url,
            {
                params:
                {
                    expand: 'true'
                }
            }
        ).pipe(catchError(this.handleError));
}

I wanted a test to check the http.get method was called. Note that there is query parameter, expand=true, the importance of which will become apparent shortly.

The failing unit test

import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { TrainInformationService } from "./traininformation.service";

describe('traininformation service', () => {
    let service: TrainInformationService;
    let httpTestingController: HttpTestingController;

    beforeEach( () => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [TrainInformationService]
        });        

        service = TestBed.get(TrainInformationService);
        httpTestingController = TestBed.get(HttpTestingController);
    });
        
    it('should perform a GET', () => {
        // arrange
        const url = service.getFullUrl('BOP');

        const urlParam = { param: 'expand', value: 'true'};
        
        const urlWithParams = `${url}?${urlParam.param}=${urlParam.value}`;
        // act (the actual act is performed by the assert!)
        service.getTrainInformation(urlWithParams).subscribe();                 
        // assert 
        const req = httpTestingController.expectOne(urlWithParams);
        
        req.flush('Get');
                
    });    
});

The beforeEach method performs the ceremony to obtain a handle on the code to test. The unit test begins with the call to it(), the URL is obtained by a method call (Tested elsewhere within the suite but not shown here) the result of which is then concatenated with the query parameters; expand = true.

The next step is to call the subscribe method of getTrainInformation. People familiar with the Jasmine testing framework will be aware this doesn’t actually do anything until a method on httpTestingController is called.

httpTestingController.expectOne is used because according to the documentation it appears to do what I needed.

Expect that a single request has been made which matches the given URL, and return its mock.

The final step is to call the flush method on the response object returned by httpTestingController.expectOne. I wasn’t sure what would be returned so I have added something to see what happens.

The test fails

When the test ran, it failed with this error:

Which I understood to mean that http.get was not being called. What was wrong with the test?

Frustration and Stackoverflow to the rescue

Initial optimism of fixing the failing test gave way to frustration after several hours of investigations and Googling the problem to death. Whilst there are many explanations for this error I didn’t understand why the test was failing. I finally asked a question on Stackoverflow and fortunately the answer provided a solution that made sense to me.

Within the code under test, the http.get method contained a query parameter, expand=true, which means httpTestingController.expectOne has to be called with a different parameter. I am still not sure why and always conscious that Select Isn’t Broken but the best reason I could find was that it might be a bug?

Changing the code under test to make the unit test “pass”

To confirm it was the query parameter causing my test to fail, I removed it from the code under test:

getTrainInformation(url: string): Observable<any> {
        return this.http.get(url            
        ).pipe(catchError(this.handleError));
}

No changes were made to the unit test code and now my test “passed”.

The problem is that whilst this test passed, the application had stopped working because the query parameter was required.

The working unit test

Following the advice given in the Stackoverflow answer I changed the parameter passed to httpTestingController.expectOne. From:

const req = httpTestingController.expectOne(urlWithParams);

To:

const req = httpTestingController.expectOne({method: 'GET'});

The complete unit test:

import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { TrainInformationService } from "./traininformation.service";

describe('traininformation service', () => {
    let service: TrainInformationService;
    let httpTestingController: HttpTestingController;

    beforeEach( () => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [TrainInformationService]
        });        

        service = TestBed.get(TrainInformationService);
        httpTestingController = TestBed.get(HttpTestingController);
    });
        
    it('should perform a GET', () => {
        // arrange
        const url = service.getFullUrl('BOP');

        const urlParam = { param: 'expand', value: 'true'};
        
        const urlWithParams = `${url}?${urlParam.param}=${urlParam.value}`;
        // act (the actual act is performed by the assert!)
        service.getTrainInformation(urlWithParams).subscribe();                 
        // assert 
        const req = httpTestingController.expectOne({method: 'GET'});
        
        req.flush('Get');
                
    });    
});

The test now passes.

Acknowledgements

Thank you AliF50 who answered my Stackoverflow question so promptly and helped me get to the bottom of this mystery.

Categories: Angular Unit Testing

oraclefrontovik

Developer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.