Это интересный вопрос.Все начинается с реализации встроенных в ядро функций.
Почему тест 5 проходит, а не тест 1
Потребовалось некоторое время, чтобы отследить.
Средой тестирования по умолчанию в Jest
является jsdom
, а jsdom
предоставляет собственную реализацию для setTimeout
.
Вызов promisify(setTimeout)
в тестовой среде jsdom
возвращает функцию, созданную запуском этого кода на setTimeout
, предоставленном jsdom
.
В отличие от этого, если Jest
работает вnode
тестовая среда, вызывающая promisify(setTimeout)
, просто возвращает встроенную реализацию node
реализацию .
Этот простой тест проходит в тестовой среде node
, но зависает в jsdom
:
const { promisify } = require('util');
test('promisify(setTimeout)', () => {
return promisify(setTimeout)(0).then(() => {
expect(true).toBe(true);
});
});
Заключение : * * * * * * * * * * * * * setTimeout
-обработанная jsdom
версия *1043* не работает.
Тест 1 и тест 5 оба проходят, если они выполняются в node
тестовой среде
Тестовый код, который использует promisify(setTimeout)
с таймерами Mocks
Звучиткак настоящий вопрос, как проверить код, как это с Timer Mocks :
app.js
const express = require("express");
const { promisify } = require("util");
const app = express();
const timeOut = promisify(setTimeout);
app.locals.message = "Original string";
app.get("/one", async (req, res) => {
await timeOut(10000); // wait 10 seconds
res.send(app.locals.message);
});
export default app;
Потребовалось некоторое время, чтобы выяснить, япройти каждую часть.
Макет promisify(setTimeout)
Невозможно протестировать код, который использует promisify(setTimeout)
, используя Таймерные макеты без насмешки promisify(setTimeout)
:
promisify(setTimeout)
можно смоделировать , создавследующий __mocks__/util.js
:
const util = require.requireActual('util'); // get the real util
const realPromisify = util.promisify; // capture the real promisify
util.promisify = (...args) => {
if (args[0] === setTimeout) { // return a mock if promisify(setTimeout)
return time =>
new Promise(resolve => {
setTimeout(resolve, time);
});
}
return realPromisify(...args); // ...otherwise call the real promisify
}
module.exports = util;
Обратите внимание, что вызов jest.mock('util');
в тесте требуется, так как util
является базовым модулем Node .
Call jest.runAllTimers () в интервале
Как оказалось, request.get
запускает целый процесс в supertest
, который использует JavaScript Event Loop и ничего не запускает до текущегорunning message (тест) завершен.
Это проблематично, поскольку request.get
в конечном итоге запустит app.get
, который затем вызовет await timeOut(10000);
, который не завершится, пока не будет вызван jest.runAllTimers
.
Все, что в синхронном тесте, будет запущено до того, как request.get
сделает что-либо, поэтому, если во время теста будет запущено jest.runAllTimers
, это не повлияет на последующий вызов await timeOut(10000);
.
Обходной путь для этой проблемы - установить интервал, который периодически ставит в очередь сообщения в цикле событий JavaScript, которые вызывают jest.runAllTimers
.Когда сообщение, вызывающее await timeOut(10000);
, запускается, оно приостанавливается в этой строке, затем запускается сообщение, вызывающее jest.runAllTimers
, и сообщение, ожидающее await timeOut(10000);
, сможет продолжить и request.get
завершится.
Capture setInterval и clearInterval
Последнее замечание: jest.useFakeTimers
заменяет глобальные функции таймера , включая setInterval
и clearInterval
, чтобы установить наш интервали очистите его, нам нужно захватить реальные функции перед вызовом jest.useFakeTimers
.
. Имея все это в виду, вот рабочий тест для кода app.js, перечисленного выше:
jest.mock('util'); // core Node.js modules must be explicitly mocked
const supertest = require('supertest');
import app from './app';
const request = supertest(app);
const realSetInterval = setInterval; // capture the real setInterval
const realClearInterval = clearInterval; // capture the real clearInterval
beforeEach(() => {
jest.useFakeTimers(); // use fake timers
});
afterEach(() => {
jest.useRealTimers(); // restore real timers
});
test("test promisify(setTimeout) with fake timers", async () => {
expect.assertions(1);
const interval = realSetInterval(() => {
jest.runAllTimers(); // run all timers every 10ms
}, 10);
await request.get("/one").then(res => {
realClearInterval(interval); // cancel the interval
expect(res.text).toEqual("Original string"); // SUCCESS
});
});