7

I'm testing an angular service with Jest.

I can to mock my httpClient, but I have a problem it is that I cannot test a url with a parameter (ex: "http://localhost:8080/api/feature/num?id=25663").

This my test code :

describe('MyService', () => {
   let myService: MyService;
   const httpMock = {get: jest.fn(() => of(dataMock))};
   const provide = (mock: any): any => mock;

   beforeEach(() => {
      myService= new MyService(provide(httpMock));
   });

   test('should created', () => {
     expect(myService).toBeTruthy();
   });

   test('should retrieve one user', (done) => {
     const number = 26586;
     const url = 'http://localhost:8080/api/feature/num?number=25663';

     myService.getNumero(number).subscribe( 
       res => {
          expect(httpMock.get).toBeCalledWith(url);
          expect(res.number).toBe(number);
          expect(res.city).toEqual('PARIS');
          done();
     });
   });
});

And in my console I have this error :

Error: Uncaught [Error: expect(jest.fn()).toBeCalledWith(...expected)

Expected: "http://localhost:8080/api/feature/num?number=25663"
Received: "http://localhost:8080/api/feature/num, {
 "params": {
    "cloneFrom": {
        "cloneFrom": null, 
        "encoder": {}, 
        "map": null, 
        "updates": null
     },
     "encoder": {}, 
     "map": null,
     "updates": [{"op": "a", "param": "number", "value": "25663"}]
   }
 }
1

3 Answers 3

6

Usually I do like this

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

...

describe('MyService', () => {
    let injector: TestBed;
    let httpMock: HttpTestingController;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [],
        });
        injector = getTestBed();
        httpMock = injector.get(HttpTestingController);
    });

    afterEach(() => {
        httpMock.verify();
    });

...

    it('should return number and city', () => {
        const expectedResult: {
            number: 26586,
            city: 'PARIS'
        };

        const service : MyService = TestBed.get(MyService);
        service.getNumero(number)
            .pipe(
                first(),
            )
            .subscribe((data) => {
                expect(data).toMatchObject(expectedResult);
            });

        const req = httpMock.expectOne(`http://localhost:8080/api/feature/num?number=25663`);
        expect(req.request.method).toBe('GET');
        req.flush(expectedResult);
    });

6
  • 1
    Tried this, and the test goes green, but you can replace 'expectedResult' in subcriber with anything, so it dosent really validate that the actual object is correct. Dont know how to solve it...
    – Michael R
    Commented Mar 17, 2022 at 10:17
  • @MichaelR this line in subscribe is checking it expect(data).toMatchObject(expectedResult); if you change it to something else it fails. for example if your service do some manipulation on the response before sending it back you need to check with that result (in this example both are same)
    – Reza
    Commented Mar 17, 2022 at 16:18
  • 1
    I've tested this setup, except I tested with a our api and switched to the observable our function returns and url and so on, also had to change 'injector.get' to 'injector.inject' since 'get' is deprecated. Its still the same issue that the expect in subscriber never runs, even if I set the done(); after the expect and in the beginning where the 'it' text is. Really strange.
    – Michael R
    Commented Mar 21, 2022 at 15:41
  • 1
    @MichaelR I also see the expect passing without data and expectedResult matching and am not sure how to solve it.
    – ETLJ
    Commented May 10, 2022 at 18:40
  • To use done you need to pass it to the calback's parameters, as described in the jasmine documentation: jasmine.github.io/tutorials/async#callbacks. However if you're using jest using the resolves operator (see my answer) is a cleaner solution. Commented Jun 9, 2022 at 15:45
1

You can use jest's resolves operator:

it('should return number and city', () => {
    const expectedResult: {
        number: 26586,
        city: 'PARIS'
    };

    const service : MyService = TestBed.get(MyService);
    expect(service.getNumero(number).toPromise())
        .resolves.toMatchObject(expectedResult);

    const req = httpMock.expectOne(`http://localhost:8080/api/feature/num?number=25663`);
    expect(req.request.method).toBe('GET');
    req.flush(expectedResult);
});
0

This is in response to the comments on the accepted answer.

I too had the same issue that when following the official Angular documentation test setup for Http requests, my expect inside was never actually executed and wasn't verifying my expected objet, however I found the solution using fakeAsync like so:

// define expected object outside tests
const expected = {...}

it('should validate request and response', fakeAsync(() => {
   // use last value RxJs operator to convert observable into promise
   lastValueFrom(service.doSomething()).then(res => expect(res).toEqual(expected));

   const req = httpTestingController.expectOne({
     method: 'GET',
     url: `${url}/doSomething`
   });

   // flush with expected body value
   req.flush(...);
   // run tick() or flush() to flush async queues and execute expected comparison
   tick();
}));

This will do the expected behavior we want. Hope it helps.

Not the answer you're looking for? Browse other questions tagged or ask your own question.