如何使用 jest/enzyme 模拟 multer 以使用 axios post 模拟调用文件上传

How to mock multer using jest/enzyme to file upload using axios post mock call

我正在使用 axios post 测试我的 express 路由器 - 调用后端。我收到 500 个响应而不是 200 个,不确定如何有效地模拟 multer。 对此有什么想法吗?谢谢

routes.jsx

const axios = require('axios')
const router = express.Router()
const multer = require('multer')
const FormData = require('form-data')
const express = require('express')

const upload = multer({ storage: multer.memoryStorage() }).any()

router.post('/', upload, (req, res) => {
  const formData = new FormData()
   const { body } = req 
    req.files.forEach(file => {
      formData.append(
        'files',
        file.buffer,
        {
          filename: file.originalname
        },
        file.originalname
      )
    })


  axios
    .post('/api/endpoint', formData)
    .then(response => {return response
    })
    .catch(e => {
    console.log(e)
    })
})

module.exports = router

下面是我的测试用例

routes.jsx.测试

const axios = require('axios')
const MockAdapter = require('axios-mock-adapter')
const myroute = require('myroute')
const app = express()
const mock = new MockAdapter(axios)
const request = require('supertest')
const express = require('express')
const bodyParser = require('body-parser')
const multer = require('multer')
jest.mock('multer')

multer.mockImplementation(() => {
  return {
    any () {
      return (req, res, next) => {
        req.body = { userName: 'testUser' }
        req.files = [
          {
            originalname: 'sample.name',
            mimetype: 'sample.type',
            path: 'sample.url'
          }
        ]
        return next()
      }
    }
  }
})
app.use(bodyParser.json())

app.use('/', myroute)

describe('sendFiles', () => {
  const url = '/api/endpoint'  

  test('200 response', () => {
    const myMockRes = { mykey: 'myVal' }
    let formData = new FormData()
    const file = new Blob(['somee contents'], { type: 'multipart/form-data' })
    formData.append('files', file)
    formData.append('userName', 'testUser')
    mock.onPost(url).reply(200, myMockRes)
    return (
      request(app)
        .post('/')
        .send({ userName: 'testUser', files: [file] })
        //.expect('Content-Type', /json/)
        .expect(200)
        .then(response => {
          const { data } = response.body
          expect(data).toEqual(myMockRes)
        })
    )
  })


})

错误:

TypeError: Cannot read property 'any' of undefined in routes.jsx

const upload = multer({ storage: multer.memoryStorage() }).any()
    

当您使用 jest.mock('multer') 时,Jest 会在测试中调用时自动模拟模块和 returns undefined。由于我们也想模拟 memoryStorageany 方法,因此我们必须通过将工厂作为第二个参数传递给 jest.mock.

来明确地做到这一点
jest.mock('multer', () => {
  const multer = () => ({
    any: () => {
      return (req, res, next) => {
        req.body = { userName: 'testUser' }
        req.files = [
          {
            originalname: 'sample.name',
            mimetype: 'sample.type',
            path: 'sample.url',
            buffer: Buffer.from('whatever'), // this is required since `formData` needs access to the buffer
          },
        ]
        return next()
      }
    },
  })
  multer.memoryStorage = () => jest.fn()
  return multer
})

另一个问题是 Blob 在 Node.js 中不存在。您可以使用 Buffer.from 生成缓冲区以在请求中发送。

const file = Buffer.from('whatever')

而且你不需要在测试中使用FormData

全部代码:

// router.test.js

const axios = require('axios')
const MockAdapter = require('axios-mock-adapter')
const express = require('express')
const app = express()
const mock = new MockAdapter(axios)
const request = require('supertest')

const bodyParser = require('body-parser')

const myroute = require('./router')

jest.mock('multer', () => {
  const multer = () => ({
    any: () => {
      return (req, res, next) => {
        req.body = { userName: 'testUser' }
        req.files = [
          {
            originalname: 'sample.name',
            mimetype: 'sample.type',
            path: 'sample.url',
            buffer: Buffer.from('whatever'),
          },
        ]
        return next()
      }
    },
  })
  multer.memoryStorage = () => jest.fn()
  return multer
})

app.use(bodyParser.json())

app.use('/', myroute)

describe('sendFiles', () => {
  const url = '/api/endpoint'

  test('200 response', () => {
    const myMockRes = { mykey: 'myVal' }
    const file = Buffer.from('whatever')
    mock.onPost(url).reply(200, myMockRes)
    return request(app)
      .post('/')
      .send({ userName: 'testUser', files: [file] })
      .expect(200)
      .then((response) => {
        const { data } = response.body
        expect(data).toEqual(myMockRes)
      })
  })
})

@Arun Kumar Mohan 让我走到了一半。好幸福的路。但是错误路径呢?阿伦说得对。你需要嘲笑这个。但是如何在回调中触发错误路径呢?提供一个你可以触发的玩笑功能可以让你进入那些错误路径。

import { UploadGuard } from '../../../src/guards';
import { Request } from 'express';

// Note: make a spy to get into the guts of the thing.
let spy = jest.fn();
jest.mock('multer', () => {
  const multer = () => ({
    any: () => {
      return (req, res, next) => {
        // Note: have the spy execute in the next.
        return next(spy());
      };
    },
  });
  multer.memoryStorage = () => jest.fn();
  return multer;
});

describe('Upload Guard', () => {
  let guard: UploadGuard;
  const loggerService = new MockLogger();

  // Note: Good practice, always reset your mocks. 
  afterEach(() => jest.resetAllMocks());

  beforeEach(() => {
    spy = jest.fn();
    guard = new UploadGuard(loggerService);
  });

  it('Should set the files on a request', async () => {
    // Given 
    const executionContext = {
      switchToHttp: () => ({
        getRequest: () =>
          ({
            headers: {
              authorization: 'Bearer FakeJWT',
              Content-Type: 'multipart/form-data',
            },
            body: {},
          } as Request),
        }),
      };
      
      // When
      await guard.canActivate(executionContext as any);

      // Then
      expect(spy).toHaveBeenCalled();
    });

    it('Throw an error if something bad happens', async () => {
      // Given
      spy.mockImplementationOnce(() => {
        // Note: Return not throw
        return new Error('Bad Things');
      });
      const executionContext = {
        switchToHttp: () => ({
          getRequest: () => ({} as Request),
        }),
      };

      try {
        // When
        await guard.canActivate(executionContext as any);
        throw new Error('Should not happen');
      } catch (err) {
        // Then
        expect(err.message).toBe('Bad Things');
      }
    });
  });