Jest 无法测试等待的承诺,而是超时
Jest can't test an awaited promise, it times out instead
我从 运行 axios GET 切换到返回 promise,现在我的 Jest 测试失败了:
正在下载 'resource.js' 中的 zip:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', fileName);
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
return new Promise((resolve, reject) => {
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.pipe(writer);
writer.on('error', (err) => {
error = err;
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.info(`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
});
}
'resource.spec.js' 中的以下测试均未通过:
it('fetches successfully data from an URL', async () => {
const onFn = jest.fn();
const data = { status: 200, data: { pipe: () => 'data', on: onFn }, headers: { 'content-length': 100 } };
const writerOnFn = jest.fn();
axios.mockImplementationOnce(() => data);
fs.createWriteStream.mockImplementationOnce(() => ({ on: writerOnFn }));
await downloadMtgJsonZip();
expect(onFn).toHaveBeenCalledWith('data', expect.any(Function));
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ url: 'https://mtgjson.com/api/v5/AllPrintings.json.zip' }),
);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ responseType: 'stream' }),
);
});
it('ticks up the progress bar', async () => {
const tickFn = jest.fn();
const dataOnFn = jest.fn((name, func) => func(['chunk']));
const data = { status: 200, data: { pipe: () => 'data', on: dataOnFn }, headers: { 'content-length': 1 } };
const writerOnFn = jest.fn();
ProgressBar.mockImplementationOnce(() => ({ tick: tickFn }));
axios.mockImplementationOnce(() => data);
fs.createWriteStream.mockImplementationOnce(() => ({ on: writerOnFn }));
await downloadMtgJsonZip();
expect(ProgressBar).toHaveBeenCalledWith(
expect.stringContaining('downloading'),
expect.objectContaining({
total: 1,
}),
);
expect(tickFn).toHaveBeenCalledWith(1);
});
});
值得注意的是,VSCode 告诉我 'resource.js' 'this expression is not callable' 中的 axios
和 nothing mockImplementationOnce
(它 'does not exist on type...').
以前我的 downloadMtgJsonZip
是这样的:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', 'AllPrintings.json.zip');
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
const timer = setInterval(() => {
if (progressBar.complete) {
const now = new Date();
console.info(`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`);
clearInterval(timer);
}
}, 100);
console.info('...starting download...');
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.pipe(writer);
}
测试中唯一不同的是 createWriteStream 的模拟更简单(它显示为 fs.createWriteStream.mockImplementationOnce(() => 'fs');
)
我试过添加:
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
我尝试添加 writerOnFn('close');
来触发 writer.on('close', ...)
。
但我一直收到这个错误:
: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
我无法弄清楚缺少什么,使异步调用成为 'invoked'。 模拟 createWriteStream
解决了我的问题,但我没有看到任何其他模拟?
如何让这些测试再次通过?
Jest 完成异步测试的默认超时为 5000 毫秒(参考:https://jestjs.io/docs/configuration#testtimeout-number)
如果您有长时间的 运行 异步调用,则有必要提高此阈值。
例如在我的 jest.config.js
中超时设置为 60000ms
module.exports = {
...
testTimeout: 60000,
}
如何在测试代码中调用使用 writer.on(event, handler)
附加的事件处理程序? writerOnFn
模拟不需要调用传入的处理函数吗?如果那些没有被调用,那么 resolve(true)
永远不会被调用,因此测试中对 await downloadMtgJsonZip();
的调用永远不会解析。
我认为你需要这样的东西
const writerOnFn = jest.fn((e, cb) => if (e === 'close') cb())
当然,您可能想要充实它以区分 'error' 和 'close' 事件,或者如果您对 'error' 进行了测试,请务必更改它条件。
我从 运行 axios GET 切换到返回 promise,现在我的 Jest 测试失败了:
正在下载 'resource.js' 中的 zip:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', fileName);
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
return new Promise((resolve, reject) => {
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.pipe(writer);
writer.on('error', (err) => {
error = err;
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.info(`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
});
}
'resource.spec.js' 中的以下测试均未通过:
it('fetches successfully data from an URL', async () => {
const onFn = jest.fn();
const data = { status: 200, data: { pipe: () => 'data', on: onFn }, headers: { 'content-length': 100 } };
const writerOnFn = jest.fn();
axios.mockImplementationOnce(() => data);
fs.createWriteStream.mockImplementationOnce(() => ({ on: writerOnFn }));
await downloadMtgJsonZip();
expect(onFn).toHaveBeenCalledWith('data', expect.any(Function));
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ url: 'https://mtgjson.com/api/v5/AllPrintings.json.zip' }),
);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ responseType: 'stream' }),
);
});
it('ticks up the progress bar', async () => {
const tickFn = jest.fn();
const dataOnFn = jest.fn((name, func) => func(['chunk']));
const data = { status: 200, data: { pipe: () => 'data', on: dataOnFn }, headers: { 'content-length': 1 } };
const writerOnFn = jest.fn();
ProgressBar.mockImplementationOnce(() => ({ tick: tickFn }));
axios.mockImplementationOnce(() => data);
fs.createWriteStream.mockImplementationOnce(() => ({ on: writerOnFn }));
await downloadMtgJsonZip();
expect(ProgressBar).toHaveBeenCalledWith(
expect.stringContaining('downloading'),
expect.objectContaining({
total: 1,
}),
);
expect(tickFn).toHaveBeenCalledWith(1);
});
});
值得注意的是,VSCode 告诉我 'resource.js' 'this expression is not callable' 中的 axios
和 nothing mockImplementationOnce
(它 'does not exist on type...').
以前我的 downloadMtgJsonZip
是这样的:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', 'AllPrintings.json.zip');
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
const timer = setInterval(() => {
if (progressBar.complete) {
const now = new Date();
console.info(`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`);
clearInterval(timer);
}
}, 100);
console.info('...starting download...');
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.pipe(writer);
}
测试中唯一不同的是 createWriteStream 的模拟更简单(它显示为 fs.createWriteStream.mockImplementationOnce(() => 'fs');
)
我试过添加:
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
我尝试添加 writerOnFn('close');
来触发 writer.on('close', ...)
。
但我一直收到这个错误:
: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
我无法弄清楚缺少什么,使异步调用成为 'invoked'。 createWriteStream
解决了我的问题,但我没有看到任何其他模拟?
如何让这些测试再次通过?
Jest 完成异步测试的默认超时为 5000 毫秒(参考:https://jestjs.io/docs/configuration#testtimeout-number)
如果您有长时间的 运行 异步调用,则有必要提高此阈值。
例如在我的 jest.config.js
中超时设置为 60000ms
module.exports = {
...
testTimeout: 60000,
}
如何在测试代码中调用使用 writer.on(event, handler)
附加的事件处理程序? writerOnFn
模拟不需要调用传入的处理函数吗?如果那些没有被调用,那么 resolve(true)
永远不会被调用,因此测试中对 await downloadMtgJsonZip();
的调用永远不会解析。
我认为你需要这样的东西
const writerOnFn = jest.fn((e, cb) => if (e === 'close') cb())
当然,您可能想要充实它以区分 'error' 和 'close' 事件,或者如果您对 'error' 进行了测试,请务必更改它条件。