在我的玩笑测试中发出错误不会按预期触发错误处理程序

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 可以防止 运行 trythrow

您可以尝试 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,
}));