【问题标题】:Emitting an error in my jest test doesn't trigger the error handler as expected在我的笑话测试中发出错误不会按预期触发错误处理程序
【发布时间】:2021-11-19 08:27:18
【问题描述】:

我的下载代码依赖于监听事件来确定何时触发回调,以及它所在的 Promise 是否应该被解析或拒绝:

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);
  });
}

根据this advice,我在编写测试时遇到了一些问题,但我设法得到了一些有用的东西,尽管感觉有点混乱:

这是我的测试,以及我设置的相关部分:

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); 代码没有通过测试。

我可能需要从根本上改变我编写测试的方式,但我不确定该怎么做。

为什么最后一次测试没有通过?

【问题讨论】:

    标签: javascript node.js jestjs event-handling


    【解决方案1】:

    当代码调用data.pipe(writer) 时,它正在运行您在测试中定义的pipeHandler 函数。这个函数接受一个给定的writer 对象并调用writer.emit(...)。我认为问题在于传入的writer 对象是为fs.createWriteStream() 模拟出来的对象,它没有定义emit 方法,因此响应该调用没有发生任何事情。它可能会引发错误,您可以在 catch 块中看到它。

    我相信您想要的是调用writerOnFn 保存的处理程序。一种方法是向您的模拟 fs.createWriteStream 返回的名为 emit 的对象添加一个属性,并将其定义为从 mockWriterEventHandlers 内部调用适当处理程序的函数。我还没有测试过这段代码,但它看起来像下面这样

    const writerEmitFn = (event, arg) => {
      mockWriterEventHandlers[event](arg);
    }
    
    fs.createWriteStream.mockImplementationOnce(() => ({
      on: writerOnFn,
      close: writerCloseFn,
      emit: writerEmitFn,
    }));
    

    【讨论】:

    • 那是……天才……!我会在早上试试 insha'Allah
    • 这行得通,但是我的测试不是出于他们自己的原因。
    【解决方案2】:

    我的猜测是 jest 正在吞噬错误。

    为了在异常情况下继续运行,jest 可以防止必须运行 trythrow

    您可以使用 jest 的 API 尝试 expecting an error to have been thrown

    【讨论】:

    • 我需要确保 writer.close 已运行,您的建议是否仍然可行?
    • 假设这个fs.createWriteStream.mockImplementationOnce 返回一个对象,该对象具有trigger 事件的方法。我正在寻找一种方法来运行writer.trigger('close')
    猜你喜欢
    • 1970-01-01
    • 2021-03-29
    • 2016-01-30
    • 2013-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多