javascript - Test af asynkron kode i Jest: done() bliver ikke kaldt som forventet


Her er en testsuite skrevet i Jest (v20.0.4).

De første 3 test forventes at opføre sig sådan, mit spørgsmål relaterer sig til Test4.

test('Test1: the list should contain 7', () => {
  const data = [1, 2, 7, 9];
  expect(data).toContain(7);
});
// > Passes as expected
test('Test2: the list should contain 7', () => {
  const data = [1, 2, 7, 9];
  expect(data).toContain(8);
});
// > Fails as expected; Expected array: [1, 2, 7, 9] To contain value: 8
test('Test3: the list should contain 7', (done) => {
  function callback(data) {
    expect(data).toContain(7);
    done();
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});
// > Passes as expected
test('Test4: the list should contain 7', (done) => {
  function callback(data) {
    expect(data).toContain(8);
    done();
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});
// > Fails with Error "Timeout - Async callback was not invoked within timeout specified"

Her er mit spørgsmål:

I Test4 kalds done() straks efter expect-sætningen.
Så selv hvis expect-sætningen ikke passer, gættede jeg på, at det skulle fejle med en fejl, der ligner Test2: (Forventet array: [1, 2, 7, 9] skal indeholde værdi: 8)

Dog fejler det med en timeout-fejl som vist ovenfor, hvilket antyder, at done() aldrig bliver kaldt.

Hvorfor? Jeg forstår det ikke!

Kan nogen venligst guide mig igennem denne adfærd?

Jeg scannede dokumenterne igennem, men kunne ikke finde noget relateret til dette.

Svar #1

I din kode kaster expect-kaldet, og done nås aldrig. Derfor venter Jest, indtil det udløber, og udsender den ubehagelige, uklare fejl, du bemærkede.

Løsningen er at fange kastet og kalde done(error) for at indikere til Jest-runneren, at testen fejlede.

test('Test4: the list should contain 7', done => {
  function callback(data) {
    try {
      expect(data).toContain(8);
      done();
    }
    catch (err) {
      done(err);
    }
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});

En kørsel af dette giver det ønskede resultat:

Error: expect(received).toContain(expected) // indexOf

Expected value: 8
Received array: [1, 2, 7, 9]

Fra dokumentationen:

Hvis expect-erklæringen fejler, kaster den en fejl, og done() bliver ikke kaldt. Hvis vi vil se i testloggen, hvorfor den fejlede, skal vi pakke expect ind i en try-blok og sende fejlen i catch-blokken til done. Ellers ender vi med en uigennemsigtig timeout-fejl, der ikke viser, hvilken værdi expect(data) modtog.

Svar #2

Jeg har fundet ud af, at setTimeout og Jest ikke fungerer, som man måske tror. Men der er en god måde at håndtere det på:

  • I starten af din test kan du fortælle Jest at bruge falske timere: jest.useFakeTimers();

  • Når du har brug for, at tilbagekaldelsen skal udføres, kan du udløse den med jest.runAllTimers();

https://jestjs.io/docs/en/timer-mocks.html