0

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 tests Commented May 19, 2020 at 19:37

1 Answer 1

1

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!'
Sign up to request clarification or add additional context in comments.

2 Comments

It appears that my initial question had 2 aspects. 1: Promise handling and flushing http data (will be handled here. Thank you very much for the explanation, it totally makes sense and is very helpful) 2: Dealing with async/await getHeaders (even after applying your suggested fix I still get "expected 1 got none" error, but only if getHeaders() is async). I will prepare a stackBiz with minimal reproduction and ask another question about in order to separate concerns.
Here is the follow up StackBlitz: testing-tn5xxm.stackblitz.io

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.