如何在笑话测试中模拟节点“createReadStream”和“readline.createInterface”

Posted

技术标签:

【中文标题】如何在笑话测试中模拟节点“createReadStream”和“readline.createInterface”【英文标题】:How to mock node 'createReadStream' and 'readline.createInterface' in jest tests 【发布时间】:2022-01-21 08:27:54 【问题描述】:

我在为涉及“createReadStream”和“readline.createInterface”的代码编写单元测试时遇到了这个问题。

下面是我需要测试的代码:

private createReadStreamSafe(filePath: string): Promise<fs.ReadStream> 
        return new Promise((resolve, reject) => 
          const fileStream = fs.createReadStream(filePath)
          console.log('file Stream')
          fileStream
            .on('error', () => 
              reject('create read stream error')
            )
            .on('open', () => 
              resolve(fileStream)
            )
        )
      
    
      async start() 
        const fileStream = await this.createReadStreamSafe(this.filePath)
    
        const rl = readline.createInterface(
          input: fileStream,
          output: process.stdout,
          terminal: false
        )
    
        for await (const line of rl) 
          ...
        
      

下面是我的测试,

it.only('should create an error if creating the read stream from the file path fails', () => 
    const mockedReadStream = new Readable()
    jest.spyOn(fs, 'createReadStream').mockReturnValue(mockedReadStream as any)
    const app = createApp('invalid/file/path')

    expect.assertions(1)
    try 
      app.start()
      mockedReadStream.emit('error', 'Invalid file path')
     catch (error) 
      expect(getErrorMessage(error)).toBe('Invalid file path')
    
  )

但是,我明白了:

node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "undefined".] 
  code: 'ERR_UNHANDLED_REJECTION'

node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);

【问题讨论】:

【参考方案1】:

模拟会导致未处理的被拒绝承诺。测试应该是异步的并返回一个承诺,即asynctry..catch 无法在同步函数中处理。

由于在调用 mockedReadStream.emit 时 promise 被拒绝,因此需要在 promise 被拒绝后不久与 catch 链接,例如通过 Jest 承诺断言:

let promise = app.start()
mockedReadStream.emit('error', 'Invalid file path')
await expect(promise).rejects.toThrow('Invalid file path')

这揭示了测试单元中的问题,因为reject() 没有传递错误。

【讨论】:

感谢@Estus。这真的很有帮助。我只是将您的最后一行代码更改为:await expect(promise).rejects.toBe('create read stream error') 并且它可以工作。顺便说一句,“创建读取流错误”是我拒绝时添加的新消息。 顺便说一句,@Estus,你知道如何模拟 'readline.createInterface' 并使其与 'for await (const line of rl)' 一起工作吗? 为了使用 for await 它应该返回异步迭代器/可迭代。一种简单的方法是在异步生成器函数中定义结果。它可能应该类似于jest.spyOn(readline, 'createInterface').mockImplementation(async function*() yield '1'; yield '2' )。 createInterface 将是常规函数,因为它是开玩笑的间谍,但它将返回异步生成器函数应该返回的异步可迭代迭代器

以上是关于如何在笑话测试中模拟节点“createReadStream”和“readline.createInterface”的主要内容,如果未能解决你的问题,请参考以下文章

如何在笑话测试中模拟 json.parse()

笑话:如何取消模拟 index.js 文件中列出的组件?

笑话:当同一个模块也有命名导出时,如何模拟默认导出组件?

笑话测试库 - 模拟拒绝承诺不能按预期在 useAsync() 中工作

笑话:节点模块依赖使用导入语句并导致测试崩溃

如何使用酶、笑话和反应测试材料设计事件监听器