CodePark/ questions / Async code, timers & time
START PRACTICINGPRACTICE
Backend question · Backend testing › Async code, timers & time

Why do both of these tests pass even when the code is broken? How do you fix them?

test('rejects unknown user', () => {
  expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});

it('loads the user', () => {
  service.findOne(1).then((u) => {
    expect(u.email).toBe('a@b.c');
  });
});

Jest waits for a test only in two cases:

  • The test function returns a promise. An async function always does.
  • The test uses the done callback.

Here, each test creates a promise but does not return it or await it. So the function returns undefined, and Jest marks the test as passed. The assertion runs after the test has already finished. Its failure, at best, shows up as a stray error after the run or inside another test.

Fix: make the tests async and await the work.

  • Write await expect(service.findOne(999)).rejects.toThrow(NotFoundException).
  • Write const u = await service.findOne(1) before you assert on u.

Returning the promise also works.

Lint rules catch this mistake:

  • valid-expect from eslint-plugin-jest
  • valid-expect-in-promise from eslint-plugin-jest
  • no-floating-promises from typescript-eslint
  • Jest waits only for a returned promise or the done callback.
  • If you don't return or await a promise, the test finishes before the assertion runs.
  • Await (or return) the promise, and use .rejects or .resolves with await.
  • Lint rules such as no-floating-promises catch this mistake.