javascript - Hur man använder JEST för att testa tidsförloppet av rekursiva funktioner
Jag skriver ett test med JEST. Jag vet inte hur man testar löftesrekursion i JEST.
I detta test är retry-funktionen som utför rekursion målet för testet tills löftet är uppfyllt.
export function retry<T>(fn: () => Promise<T>, limit: number = 5, interval: number = 1000): Promise<T> {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
setTimeout(() => {
// Reject if the upper limit number of retries is exceeded
if (limit === 1) {
reject(error);
return;
}
// Performs recursive processing of callbacks for which the upper limit number of retries has not been completed
try {
resolve(retry(fn, limit - 1, interval));
} catch (err) {
reject(err);
}
}, interval);
});
});
}
Utför följande test på ovanstående omsökningsfunktion.
- retry() löses vid tredje körningen. Första, andra och tredje gången kallas var 1000 sekund respektive.
Jag trodde att det skulle vara som följer när man skriver dessa i JEST.
jest.useFakeTimers();
describe('retry', () => {
// Timer initialization for each test
beforeEach(() => {
jest.clearAllTimers();
});
// Initialize timer after all tests
afterEach(() => {
jest.clearAllTimers();
});
test('resolve on the third call', async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error('Async error'))
.mockRejectedValueOnce(new Error('Async error'))
.mockResolvedValueOnce('resolve');
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
await retry(fn);
// Advance Timer for 1000 ms and execute for the second time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(2);
// Advance Timer for 1000 ms and execute for the third time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(3);
await expect(fn).resolves.toBe('resolve');
});
});
Som ett resultat misslyckades det på följande fel.
● retry › resolve on the third call
Timeout - Async callback was not invoked within the 30000ms timeout specified by jest.setTimeout.Error:
> 16 | test('resolve on the third call', async () => {
| ^
17 | jest.useFakeTimers();
18 | const fn = jest
19 | .fn()
Jag tror att det kommer att vara hanterbart i JEST:s miljö när det gäller detta fel. Men i grunden vet jag inte hur man testar asinchron rekursiv bearbetning av promise i JEST.
Svar #1
Din funktion är så svår att testa med timer.
När du anropar await retry(fn); betyder det att du kommer vänta tills retry returnerar ett värde, men setTimeout har blockerats tills du anropar jest.advanceTimersByTime(1000); => detta är huvudorsaken, eftersom jest.advanceTimersByTime(1000); aldrig har anropats.
Du kan se mitt exempel, det fungerar bra med jests falska timers.
test("timing", async () => {
async function simpleTimer(callback) {
await callback();
setTimeout(() => {
simpleTimer(callback);
}, 1000);
}
const callback = jest.fn();
await simpleTimer(callback); // it does not block any things
for (let i = 0; i < 8; i++) {
jest.advanceTimersByTime(1000); // then, this line will be execute
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
expect(callback).toHaveBeenCalledTimes(9); // SUCCESS
});
Jag tycker att du kan hoppa över testtimer-detaljerna, testa bara din logik: fn har kallats 3 gånger, och slutligen returnerar den "resolve"
test("resolve on the third call", async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error("Async error"))
.mockRejectedValueOnce(new Error("Async error"))
.mockResolvedValueOnce("resolve");
// expect.assertions(3);
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
const result = await retry(fn);
expect(result).toEqual("resolve");
expect(fn).toHaveBeenCalledTimes(3);
});
Obs: ta bort alla dina falska timers - jest.useFakeTimers
Svar #2
Dokumentationen för Jest antyder att testning av löften är ganska enkelt (https://jestjs.io/docs/en/asynchronous)
De ger detta exempel (anta att fetchData returnerar ett löfte precis som din retry-funktion)
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
