NestJS、TypeScript、Jest -> TypeError:无法读取未定义的属性“管道”

问题描述

案例是为 uploadFile 编写单元测试,它应该使用 Jest 将文件上传到 Google Storage 存储桶,并在 File 对象上模拟 createReadStream 函数

my-service.ts

async uploadFile(file: FileUpload): Promise<{
url: string
path: string}> {
 try {
  file.createReadStream().pipe(
   bucket
    .createWriteStream({
     ...some options
    })
    .on('error',(err) => {
     reject(err)})
    .on('finish',async () => {
     resolve({
      url: 'file-url',path: 'file-path'
     })
    })
   
 }
}

my-service.spec.ts

  describe('#uploadFile',() => {
    it('uploads file',async () => {
      const bucketMock = new Bucket('the-bucket-mock')
      const bucketFileMock = new File(bucketMock,'the-file')

      const fileUploadMock = {
        filename: 'the-file',mimetype: 'mimetype',encoding: 'encoding',createReadStream: jest.fn().mockImplementation((stream) => {
          pipe: jest.fn()
        }),}

      jest
        .spyOn(fileUploadMock,'createReadStream')
        .mockImplementation((stream) => {
          stream.pipe()
          return Promise.resolve({
            url: 'url-result',path: 'file-path-result',})
        })

      const uploadFileResult = await myService.uploadFile(fileUploadMock)

      expect(uploadFileResult).toBeCalled()
    })
  })

解决方法

这部分代码:

        createReadStream: jest.fn().mockImplementation((stream) => {
          pipe: jest.fn()
        }),

没有做你认为它在做的事情。您相信您传递给 mockImplementation 的函数正在返回一个看起来像 {pipe: jest.fn()} 的对象,但是,事实并非如此。如果箭头函数在箭头之后遇到的第一件事是一个开放的大括号,那么现在告诉 TS/JS 你在一个函数体内,它不再有隐式返回。类似于如果您写道:

// Nothing happens here,and it returns undefined
function (stream) {
  pipe: jest.fn()
}

对此的解决方法是:

(stream) => {
  return { pipe: jest.fn() };
}

或者如果你想保持箭头运算符的简洁性,你只需要确保箭头后面的第一件事不是大括号,即:

// Parenthesis help! 
(stream) => ({
  pipe: jest.fn()
})