Reputation: 417
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
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
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