Testing async HTTP-based function with Jasmine and await: Expected one matching request for criteria “…”, found none

一个人想着一个人 提交于 2020-06-29 05:20:54

问题


WHAT: Testing an async function that uses await

WITH: Angular 9, Jasmine 3.4.0

Minimal code to reproduce: StackBlitz

I have a function like this. Note, that it has to await this.getHeaders(). Whenever I remove await and replace the implementation of getHeaders() by some synchonous implementation, the test runs successfully.

What is the correct way to test it?

private async loadUserData() {
    // Try to remove await - the test runs successfully
    //const headers = this.getHeaders();
    const headers = await this.getHeaders();
    return this.httpClient
      .get<UserData>(window.location.origin + '/12345', { headers })
      .toPromise()
      .then((data) => {
        this.userData = data;
      })
      .catch((e) => {
        console.log('Oh noooo...');
      });
  }

WHAT I'VE TRIED:

  • It might be the case that the url in StackBitz is not correct, but when testing locally I am sure it is, so this should not be the root cause
  • not sure, if fakeAsync() will help - the "pure" http test works...

NOT A DUPE OF:

  • Not sure if timeout on flush would help in my case
  • I don't use any params on my url

回答1:


You're very close. Your test is now structured properly but you need some testing utilities provided by Angular to make sure that your Promises are resolved in the correct order for your expectations to be correct.

Ultimately the problem relates to Angular zones. Because you're structuring promises in your service, and that promise resolution must occur before an outgoing request occurs. You call loadUserData in your test, and then on the next line you write an assertion that says "make sure that this request happened, if not that's an error". When you write your header retrieval function in such a way that you don't use an async primitive (like Promise or Observable) this request happens "immediately". But when you use a Promise, no request happens "immediately". Instead, it has to resolve your header retrieval function first.

Luckily, your test failing is just a phantom feature of the test environment and not a bug in your implementation. Like I said, Angular gives you some testing tools to make sure you can "flush" all pending promises before writing an assertion.

import { fakeAsync,  tick } from '@angular/core/testing';

// instead of using `done`, which is just fine btw, we wrap our test function
// in fakeAsync(). This let's us have fine grained control over stepping through
// our code under test
it('should return user data on success', fakeAsync(() => {
  const mockUserData : UserData = {
    name: 'John',
      surname: 'Do',
      id: '12345'
    };

    (service as any).loadUserData().then(() => {
      expect((service as any).userData).toEqual(mockUserData);
      //expect(req.request.method).toEqual('GET');
    });

    // tick() steps through the next round of pending asynchronous activity
    // this will also step through 'setTimeout' and 'setInterval' code
    // you may have in your service, as well as Observable code
    tick();
    const req = httpTestingController.expectOne(
      'https://testing-qh48mg.stackblitz.io/12345',
    );
    req.flush(mockUserData);
}));

Updated Stackblitz. Docs on fakeAsync. Related question on testing Http code in Angular using tick.



来源:https://stackoverflow.com/questions/62007915/testing-async-http-based-function-with-jasmine-and-await-expected-one-matching

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!