mark
mark

Reputation: 417

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

I am testing my express router with axios post-call to backend. I am getting 500 responses instead of 200, not sure how to mock the multer effectively. Any thoughts on this? Thanks

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

Below are my test case

routes.jsx.test

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


})

error:

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

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

Upvotes: 3

Views: 9145

Answers (2)

Mardok
Mardok

Reputation: 1460

@Arun Kumar Mohan got me halfway there. Good happy path. But what about the error path? Arun has it right. You need to mock this. But what about triggering error paths within the callback? Providing a jest function that you can trigger can get you into those error paths.

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

Upvotes: 0

Arun Kumar Mohan
Arun Kumar Mohan

Reputation: 11915

When you use jest.mock('multer'), Jest automatically mocks the module and returns undefined when it gets called in the test. Since we want to mock memoryStorage and any methods as well, we have to do it explicitly by passing a factory as the second argument to 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
})

The other issue is that Blob does not exist in Node. You can use Buffer.from to generate a buffer to send in the request.

const file = Buffer.from('whatever')

And you don't need to use FormData in the test.

The whole code:

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

Upvotes: 6

Related Questions