0
votes

WHAT: Testing an async function returning a Promise that relies on nested http calls

WITH: Angular 9, Jasmine 3.4.0

PROBLEM: Error: Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) at Error: Expected no open requests, found 1: GET http://localhost:9876/24a27be6-9f62-4156-8fd4-adcd945ec909

import ...

export interface UserData {...}

@Injectable()
export class UserDataManagementService {
  private userID: string | undefined;
  public userData: UserData;

  constructor(...) {}

  private async loadUserData() {
    const headers = await this.getHeaders();
    return this.httpClient
      .get<UserData>(window.location.origin + '/' + this.userID, { headers })
      .toPromise()
      .then((data) => {
         this.userData = data;
      })
      .catch((e) => {
        this.logger.error('Error in loadUserData: ', e);
      });
  }

  private async getHeaders() {...}
}

Here is my test:

import ...
const mockUserData: UserData = {...};

describe('UserDataManagementService', () => {
  let httpTestingController: HttpTestingController;
  let service: UserDataManagementService;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        UserDataManagementService,
        ...
      ],
    });
    httpTestingController = TestBed.get(HttpTestingController);
    service = TestBed.get(UserDataManagementService);
 });

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


describe('loadUserData', () => {
    it('should return user data on success', (done) => {
      (service as any).userID = '24a27be6-9f62-4156-8fd4-adcd945ec909';
      (service as any).loadUserData().then(() => {
        expect((service as any).userData).toEqual(mockUserData);
        const req = httpTestingController.expectOne( 
          'http://localhost:9876/24a27be6-9f62-4156-8fd4-adcd945ec909',
        );
        expect(req.request.method).toEqual('GET');
        req.flush(mockUserData);
        done();
      });
    });
  });
});

WHAT HAVE I TRIED SO FAR:

  • Tried to increase default timeout to 20000ms - did not work
  • Different combinations of done / async / await etc.
  • Placement of httpTestingController.expectOne outside of then() (does not really make sense, but I've tried everything)
  • If I put httpTestingController.verify() into this test, the error "expected no open requests..." disappears

THIS IS NOT A DUPLICATE OF THE FOLLOWING:

  • My function returns a promise, like here
  • I use done(), like here
  • I have no long-running task like here
  • fakeAsync does not help me neither...
1
is this code for production or just for fun? for production you can separate some details into other services and reduce complexity of your testsRadik

1 Answers

1
votes

While I can't reproduce your test environment, it appears that your test is timing out because your mock request is never fulfilled. The reason it's never fulfilled is because you flush the test request in the callback designed to handle a response.

(service as any).loadUserData().then(() => {
        const req = httpTestingController.expectOne(
          'http://localhost:9876/24a27be6-9f62-4156-8fd4-adcd945ec909',
        );
        expect((service as any).userData).toEqual(mockUserData);
        expect(getHeadersSpy).toHaveBeenCalled();
        expect(req.request.method).toEqual('GET');
        // this line tells the test scaffolding to fulfill the request
        req.flush(mockUserData); 
        done();
      });

I expect that if you refactor to flush the request, then your test will not time out (might still fail for other reasons).

const req = httpTestingController.expectOne('http://localhost:9876/24a27be6-9f62-4156-8fd4-adcd945ec909');
(service as any).loadUserData().then(() => {
    expect((service as any).userData).toEqual(mockUserData);
    expect(getHeadersSpy).toHaveBeenCalled();
    expect(req.request.method).toEqual('GET');
    done();
});

req.flush(mockUserData); // <= essentially: 'call my callback now!'