Hopp til hovedinnhold
Versjon: 30.0

Timer-mokker

Unofficial Beta Translation

This page was AI-translated by PageTurner (beta). Not officially endorsed by the project. Found an error? Report issue →

De native timer-funksjonene (f.eks. setTimeout(), setInterval(), clearTimeout(), clearInterval()) er ikke ideelle i testmiljøer siden de avhenger av faktisk tidspassering. Jest kan erstatte timere med funksjoner lar deg kontrollere tidens gang. Great Scott!

info

Se også dokumentasjonen for Falske Timere API.

Aktiver falske timere

I følgende eksempel aktiverer vi falske timere ved å kalle jest.useFakeTimers(). Dette erstatter den opprinnelige implementasjonen av setTimeout() og andre timerfunksjoner. Timere kan gjenopprettes til normal oppførsel med jest.useRealTimers().

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');

test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();

expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

Kjør alle timere

En annen test vi kanskje vil skrive for denne modulen er en som bekrefter at tilbakekallingen skjer etter 1 sekund. For å gjøre dette bruker vi Jests API for tidskontroll for å spole tiden fremover midt i testen:

jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();

// Fast-forward until all timers have been executed
jest.runAllTimers();

// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Kjør ventende timere

Det finnes også scenarier hvor du kan ha en rekursiv timer – det vil si en timer som setter en ny timer i sin egen tilbakekalling. For disse vil kjøring av alle timere skape en endeløs løkke, som gir feilen: "Avbryter etter å ha kjørt 100000 timere, antar uendelig løkke!"

Hvis dette gjelder deg, vil bruk av jest.runOnlyPendingTimers() løse problemet:

infiniteTimerGame.js
function infiniteTimerGame(callback) {
console.log('Ready....go!');

setTimeout(() => {
console.log("Time's up! 10 seconds before the next game starts...");
callback && callback();

// Schedule the next game in 10 seconds
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}

module.exports = infiniteTimerGame;
__tests__/infiniteTimerGame-test.js
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');

describe('infiniteTimerGame', () => {
test('schedules a 10-second timer after 1 second', () => {
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();

infiniteTimerGame(callback);

// At this point in time, there should have been a single call to
// setTimeout to schedule the end of the game in 1 second.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
jest.runOnlyPendingTimers();

// At this point, our 1-second timer should have fired its callback
expect(callback).toHaveBeenCalled();

// And it should have created a new timer to start the game over in
// 10 seconds
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
notat

For feilsøking eller andre grunner kan du endre grensen for antall timere som kjøres før feil kastes:

jest.useFakeTimers({timerLimit: 100});

Forfrem timere med tid

Et annet alternativ er å bruke jest.advanceTimersByTime(msToRun). Når dette API-et kalles, forfremmes alle timere med msToRun millisekunder. Alle ventende "makro-oppgaver" i køen fra setTimeout() eller setInterval() som skulle utføres i dette tidsvinduet, vil kjøres. Videre, hvis disse makro-oppgavene planlegger nye makro-oppgaver som skal utføres i samme tidsvindu, vil disse også kjøres til ingen flere makro-oppgaver gjenstår i køen for angitt tidsvindu.

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
jest.useFakeTimers();
it('calls the callback after 1 second via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();

// Fast-forward until all timers have been executed
jest.advanceTimersByTime(1000);

// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Til slutt kan det av og til være nyttig i tester å kunne tømme alle ventende timere. Til dette har vi jest.clearAllTimers().

Forfrem timere til neste bilde

I applikasjoner ønsker man ofte å planlegge arbeid innenfor en animasjonsramme (med requestAnimationFrame). Vi tilbyr en praktisk metode jest.advanceTimersToNextFrame() for å forfremme timere nok millisekunder til å utføre alle aktivt planlagte animasjonsrammer.

For falsk tidsberegning utføres animasjonsrammer hvert 16ms (tilsvarer ca 60 bilder per sekund) etter klokkestart. Når du planlegger en tilbakekalling i en animasjonsramme (med requestAnimationFrame(callback)), vil callback kjøres når klokken har gått 16ms. jest.advanceTimersToNextFrame() forfremmer klokken akkurat nok til å nå neste 16ms-intervall. Hvis klokken allerede har gått 6ms siden en animasjonsramme-callback ble planlagt, vil klokken forfremmes med 10ms.

jest.useFakeTimers();
it('calls the animation frame callback after advanceTimersToNextFrame()', () => {
const callback = jest.fn();

requestAnimationFrame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();

jest.advanceTimersToNextFrame();

// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Selektiv forfalskning

Noen ganger kan koden din kreve å unngå å overskrive den originale implementasjonen av et bestemt API. I så fall kan du bruke doNotFake-alternativet. For eksempel, slik kan du gi en tilpasset mock-funksjon for performance.mark() i jsdom-miljø:

/**
* @jest-environment jsdom
*/

const mockPerformanceMark = jest.fn();
window.performance.mark = mockPerformanceMark;

test('allows mocking `performance.mark()`', () => {
jest.useFakeTimers({doNotFake: ['performance']});

expect(window.performance.mark).toBe(mockPerformanceMark);
});