在我的玩笑测试中发出错误不会按预期触发错误处理程序
Emitting an error in my jest test doesn't trigger the error handler as expected
我的下载代码依赖于侦听事件来确定何时触发回调,以及它所在的承诺是否应该被解决或拒绝:
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) => {
const timeout = 20000;
const timer = setTimeout(() => {
console.log('timed out'); // debug log
writer.close();
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
// set up data and writer listeners
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.on('error', (err) => { // added this to see if it would be triggered - it is not
console.log(`did a data error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('error', (err) => {
console.log(`did a writer error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.log(`close called: ${now}`);
console.log(`error is: ${error}`);
console.info(
`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`,
);
clearTimeout(timer);
console.log(`time cleared: ${timer}`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
// finally call data.pipe with our writer
data.pipe(writer);
});
}
我在编写测试时遇到了一些问题,但我设法得到了一些有用的东西,尽管感觉有点乱,基于 :
这是我的测试,包含我设置的相关部分:
describe('fetchData', () => {
let dataChunkFn;
let dataErrorFn;
let dataOnFn;
let writerCloseFn;
let writerErrorFn;
let writerOnFn;
let pipeHandler;
beforeEach(() => {
// I've left all the mocking in place,
// to give an idea of what I've set up
const mockWriterEventHandlers = {};
const mockDataEventHandlers = {};
dataChunkFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataErrorFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataOnFn = jest.fn((e, cb) => {
mockDataEventHandlers[e] = cb;
});
writerCloseFn = jest.fn(() => mockWriterEventHandlers.close());
writerErrorFn = jest.fn(() => mockWriterEventHandlers.error());
writerOnFn = jest.fn((e, cb) => {
mockWriterEventHandlers[e] = cb;
});
const getMockData = (pipe) => ({
status: 200,
data: {
pipe,
on: dataOnFn,
},
headers: { 'content-length': 100 },
});
axios.mockImplementationOnce(() => getMockData(pipeHandler));
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
}));
jest.spyOn(console, 'info').mockImplementation(() => {});
jest.spyOn(console, 'log').mockImplementation(() => {});
});
it.only('handles errors from the writer', async (done) => {
console.log('writer error');
expect.assertions(1);
pipeHandler = (writer) => writer.emit('error', new Error('bang'));
try {
await downloadMtgJsonZip();
done.fail('ran without error');
} catch (exception) {
// expect(dataErrorFn).toHaveBeenCalled(); // neither of these are called
expect(writerErrorFn).toHaveBeenCalled();
}
});
我本以为,当 data(pipe)
运行 并且编写器发出新错误时,它至少会触发一个错误侦听器。
代码 运行 符合预期,它甚至可以处理超时(我最初设置的太低),但最后一个测试没有 运行。
正如我在上面评论的那样,上面的函数都没有被调用,所以 expect.assertions(1);
代码没有通过测试。
我可能需要从根本上改变我编写测试的方式,但我不确定我会怎么做。
为什么上次测试没有通过?
我的猜测是 jest
正在吞噬错误。
为了在出现异常的情况下继续 运行ning,jest
可以防止 运行 try
和 throw
。
您可以尝试 expecting an error to have been thrown 使用 jest 的 API。
当代码调用 data.pipe(writer)
时,它是 运行 您在测试中定义的 pipeHandler
函数。此函数接受给定的 writer
对象并调用 writer.emit(...)
。我认为问题是传入的 writer
对象是为 fs.createWriteStream()
模拟的对象,它没有定义 emit
方法,因此没有任何反应发生称呼。它可能会抛出一个错误,您可以在 catch 块中看到它。
我相信您想要的是调用 writerOnFn
保存的处理程序。一种方法是将 属性 添加到名为 emit
的模拟 fs.createWriteStream
返回的对象中,并将其定义为从内部调用适当处理程序的函数 mockWriterEventHandlers
.我还没有测试过这段代码,但它看起来像下面这样
const writerEmitFn = (event, arg) => {
mockWriterEventHandlers[event](arg);
}
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
emit: writerEmitFn,
}));
我的下载代码依赖于侦听事件来确定何时触发回调,以及它所在的承诺是否应该被解决或拒绝:
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) => {
const timeout = 20000;
const timer = setTimeout(() => {
console.log('timed out'); // debug log
writer.close();
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
// set up data and writer listeners
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.on('error', (err) => { // added this to see if it would be triggered - it is not
console.log(`did a data error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('error', (err) => {
console.log(`did a writer error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.log(`close called: ${now}`);
console.log(`error is: ${error}`);
console.info(
`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`,
);
clearTimeout(timer);
console.log(`time cleared: ${timer}`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
// finally call data.pipe with our writer
data.pipe(writer);
});
}
我在编写测试时遇到了一些问题,但我设法得到了一些有用的东西,尽管感觉有点乱,基于
这是我的测试,包含我设置的相关部分:
describe('fetchData', () => {
let dataChunkFn;
let dataErrorFn;
let dataOnFn;
let writerCloseFn;
let writerErrorFn;
let writerOnFn;
let pipeHandler;
beforeEach(() => {
// I've left all the mocking in place,
// to give an idea of what I've set up
const mockWriterEventHandlers = {};
const mockDataEventHandlers = {};
dataChunkFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataErrorFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataOnFn = jest.fn((e, cb) => {
mockDataEventHandlers[e] = cb;
});
writerCloseFn = jest.fn(() => mockWriterEventHandlers.close());
writerErrorFn = jest.fn(() => mockWriterEventHandlers.error());
writerOnFn = jest.fn((e, cb) => {
mockWriterEventHandlers[e] = cb;
});
const getMockData = (pipe) => ({
status: 200,
data: {
pipe,
on: dataOnFn,
},
headers: { 'content-length': 100 },
});
axios.mockImplementationOnce(() => getMockData(pipeHandler));
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
}));
jest.spyOn(console, 'info').mockImplementation(() => {});
jest.spyOn(console, 'log').mockImplementation(() => {});
});
it.only('handles errors from the writer', async (done) => {
console.log('writer error');
expect.assertions(1);
pipeHandler = (writer) => writer.emit('error', new Error('bang'));
try {
await downloadMtgJsonZip();
done.fail('ran without error');
} catch (exception) {
// expect(dataErrorFn).toHaveBeenCalled(); // neither of these are called
expect(writerErrorFn).toHaveBeenCalled();
}
});
我本以为,当 data(pipe)
运行 并且编写器发出新错误时,它至少会触发一个错误侦听器。
代码 运行 符合预期,它甚至可以处理超时(我最初设置的太低),但最后一个测试没有 运行。
正如我在上面评论的那样,上面的函数都没有被调用,所以 expect.assertions(1);
代码没有通过测试。
我可能需要从根本上改变我编写测试的方式,但我不确定我会怎么做。
为什么上次测试没有通过?
我的猜测是 jest
正在吞噬错误。
为了在出现异常的情况下继续 运行ning,jest
可以防止 运行 try
和 throw
。
您可以尝试 expecting an error to have been thrown 使用 jest 的 API。
当代码调用 data.pipe(writer)
时,它是 运行 您在测试中定义的 pipeHandler
函数。此函数接受给定的 writer
对象并调用 writer.emit(...)
。我认为问题是传入的 writer
对象是为 fs.createWriteStream()
模拟的对象,它没有定义 emit
方法,因此没有任何反应发生称呼。它可能会抛出一个错误,您可以在 catch 块中看到它。
我相信您想要的是调用 writerOnFn
保存的处理程序。一种方法是将 属性 添加到名为 emit
的模拟 fs.createWriteStream
返回的对象中,并将其定义为从内部调用适当处理程序的函数 mockWriterEventHandlers
.我还没有测试过这段代码,但它看起来像下面这样
const writerEmitFn = (event, arg) => {
mockWriterEventHandlers[event](arg);
}
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
emit: writerEmitFn,
}));