After a lot of efforts i was not able to figure this one out and hence planned to get the help. I am using a middleware in my node+ express app which looks like :
import mainConfig from '../mainConfig/index';
const axios = require('axios');
module.exports = {
authHandler: (req, res, next) => {
return mainConfig.initialize().then(() => {
const apiUri = mainConfig.get('app.api');
if (apiUri) {
return axios.get(apiUri).then(response => {
next();
}).catch(error => {
res.redirect('/expired');
throw new Error(error);
});
}
}).catch(() => {
});
}
};
For this, I have written the test case in which I was able to mock the axios and my mainCongig module. Now, I want to test whether next() was called as the request was resolved for axios. Can someone help me with the same?
test case I have written is :
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
You have to wait for the middleware to resolve. As you are returning a promise from your middleware, you can wait in the test with an await statement:
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', async () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
await middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
Note that in order to be able to use the await keyword you need to define your test with async.
I'm not an expert, but as far as I know you are testing asynchronous code. So you have to use the done() keyword. Lookup this for more information: https://jestjs.io/docs/en/asynchronous
Related
I'm learning nodejs and for the most part its going well. Im trying to learn how to do mocking in tests with jest. I've watched numerous tutorials but I cant seem to get my head around it.
I have this middleware that is used on protected routes...
import jwt from 'jsonwebtoken';
export default function (req, res, next) {
const token = req.header('x_auth-token');
if (!token) return res.status(401).json({ message: 'Access denied' });
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
return res.status(400).send('Invalid Token');
}
}
From what I've read, I think the approach Im supposed to to take is something like this...
import verifyToken from '../middleware/verifyToken';
test('verifyToken', () => {
expect.assertions(1);
const res = {};
const req = {};
const next = (err) => expect(err).toBeFalsy();
verifyToken(req, res, next);
});
However this clearly doesnt work.
So how do I mock the request header with a token?
So if we comletely forget about what req, res are in the real world, obviously they are request and response, but lets just forget about that for now.
in your real code you have token = req.header("x_auth-token")
So in our test code, we need to have something in the req object that when called with those parameters returns what you want.
So I would say.
const req = {
header: jest.fn(() => 'myAuthToken')
}
the jest.fn() has mad a mock function and when it is invoked it will always return the string myAuthToken.
We can then check that the header function has been called with the correct params by adding
expect(req.header).toHaveBeenCalledWith("x_auth-token")
You are also going to need to mock jwt.verify as you are not testing that jwt.verify works as that will be covered it its own tests. You will want to make sure that you are using the response of that correctly
To do that take a look at this stack overflow question
And finally I would set next as a mock function
mockNext = jest.fn()
Then we can say in the test
expect(mockNext).toHaveBeenCalled()
So I was having trouble understandin how to mock functions. I did a bunch of googling based on on Ollie Pugh's answer and this is what I came up with.
import jwt from 'jsonwebtoken';
import verifyToken from '../middleware/verifyToken';
import { uIds } from './testdata/userTestData';
describe('verifyToken tests', () => {
const { uid1 } = uIds;
it('Should pass the userId to the request object if token is verified', () => {
const res = {};
const req = {
header: jest.fn(() => 'myAuthToken'),
};
const next = jest.fn();
const verify = jest
.spyOn(jwt, 'verify')
.mockReturnValueOnce({ userId: String(uid1) });
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).toEqual({ userId: String(uid1) });
expect(next).toHaveBeenCalled();
});
it('Should deny access if token is not present in header', () => {
const res = {
json(msg) {
expect(msg).toEqual({ message: 'Access denied' });
},
status(responseStatus) {
expect(responseStatus).toEqual(401);
return this;
},
};
const req = {
header: jest.fn(),
};
const next = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
it('Should deny access if token is invalid', () => {
const res = {
send(text) {
expect(text).toEqual('Invalid Token');
},
status(responseStatus) {
expect(responseStatus).toEqual(400);
return this;
},
};
const req = {
header: jest.fn(() => 123),
};
const next = jest.fn();
const verify = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
});
This passes the tests. However I'm not sure about the validity of the test.
Hi I'm trying to test a post route that make loots of operations using Jest.
I'm trying to mock a class that makes a http call to another API. The problem is the response is always undefined and I have no idea whats wrong.
This is my mock comunicante-service.js (located in root/mock):
module.exports = {
getConfig: jest.fn(async () => {
Promise.resolve(dadosContaConfig);
}),
sendMessage: jest.fn(async () => {
Promise.resolve('ok');
}),
};
And that's the test
jest.mock('../src/services/comunicante-service');
describe('test /message', () => {
it('no token', async () => {
const res1 = await request(app).post('/message').set(config);
expect(res1.statusCode).toEqual(400);
});
inside of the code that handle the /result there's a call to
const aux = await comunicanteService.getConfig(conta_id);
It looks like you may have forgotten to return something from the mock service. You could try:
module.exports = {
getConfig: jest.fn(async () => {
return Promise.resolve(dadosContaConfig);
}),
sendMessage: jest.fn(async () => {
return Promise.resolve('ok');
}),
};
This can be simplified to:
module.exports = {
getConfig: jest.fn().mockResolvedValue(dadosContaConfig),
sendMessage: jest.fn().mockResolvedValue('ok'),
};
I'm doing a POST to create an item and send the newly created item as response back to the client.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
}
Now I also want to send out notifications after creating an item but also after responding to the client - to make the request as fast as possible.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
}
If I want to test this using jest + supertest, this is how it'd look:
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
});
}
But how could I test if the sendNotification() was called?
Ok, not perfect but working right now:
I added a call to an external method from another package at the end of the async request-handler. I know that you shouldn't add code just for testing purposes but I prefered this to random setTimeouts in my tests.
hooks.js
const deferreds = [];
exports.hookIntoEnd = () => {
const p = new Promise((resolve, reject) => {
deferreds.push({ resolve, reject });
});
return p;
};
exports.triggerEndHook = () => {
if (Array.isArray(deferreds)) {
deferreds.forEach(d => d.resolve());
}
};
handler.js
const { triggerEndHook } = require('./hooks');
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
// this is only here so that we can hook into here from our tests
triggerEndHook();
}
test.js
test('run + test stuff after res.send', async () => {
const server = require('../index');
const { hookIntoEnd } = require('../hooks');
const aws = require('../utils/aws');
const getObjectMetadataSpy = jest
.spyOn(aws, 'getObjectMetadata')
.mockImplementation(() => Promise.resolve({ Metadata: { a: 'b' } }));
const p = hookIntoEnd();
const response = await request(server)
.post('/api/items')
.send({ foo: 'bar' })
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toEqual({ id: 1, name: 'test item'});
// test for code that was run after res.send
return p.then(async () => {
console.log('>>>>>>>>>>> triggerEndHook');
expect(getObjectMetadataSpy).toHaveBeenCalledTimes(2);
});
});
You can use mocking in Jest to spy on the sendNotification() function and assert that it has been called. A simple example:
const sendNotification = require('./sendNotification');
const sendNotificationSpy = jest.spyOn(sendNotification);
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
expect(sendNotificationSpy).toHaveBeenCalled();
});
}
After res.send() is called the program calls someService.method({param1}) function.
Using sinon to spy that service method:
it('test after send', function(done){
const spy = sinon.spy(someService, 'method');
agent
.set('Authorization', token)
.post('/foo')
.expect(200)
.then(() => {
return setTimeout(function() {
// Assert the method was called once
sinon.assert.callCount(spy, 1);
// Assert the method was called with '{param1}' parameter
sinon.assert.calledWith(spy, {param1});
// Test callback!
done();
}, 100);
});
});
- Using setTimeout with the minimus time (ms) as possible to wait for the method to be called.
Recommendations and improvements will be appreciated! (I'm still trying to avoid using arbitrary amount of timeout)
I'm writing unit tests for separate middleware functions in Node/Express using Jest.
A simple example of the middleware:
function sendSomeStuff(req, res, next) {
try {
const data = {'some-prop':'some-value'};
res.json(data);
next();
} catch (err) {
next(err);
}
}
And a sample of my test suite:
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
let response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
// How to 'capture' what is sent as JSON in the function?
});
});
});
I have to provide a callback to populate the next parameter, which is called in the function. Normally, this would 'find the next matching pattern', and pass the req and res objects to that middleware. However, how can I do this in a test set-up? I need to verify the JSON from the response.
I don't want to touch the middleware itself, it should be contained in the test environment.
Am I missing something here?
Found a fix!
Leaving this here for someone else who might struggle with the same.
When returning data using res.send(), res.json() or something similar, the response object (from const response = httpMocks.createResponse();)
itself is updated. The data can be collected using res._getData():
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
const response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
});
const { property } = JSON.parse(response._getData());
expect(property).toBe('someValue');
});
});
});
I did a different way by utilising jest.fn(). For example:
if you wanna test res.json({ status: YOUR_RETURNED_STATUS }).status(200);
const res = {};
res.json = jest.fn(resObj => ({
status: jest.fn(status => ({ res: { ...resObj, statusCode: status }
})),
}));
Basically, I mock the res chain methods(json and status).
That way you can do expect(YOUR_TEST_FUNCTION_CALL).toEqual({ res: { status: 'successful', statusCode: 200 }}); if your response structure is like that.
I wanna test a middleware function that inside calls a vendor middleware function. The middleware is:
const expressJwt = require('express-jwt');
const validateJwt = expressJwt({ secret: 'whatever' });
exports.isAuthenticated = (req, res, next) => {
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = `Bearer ${req.query.access_token}`;
}
validateJwt(req, res, next);
};
I've tried to create a sinon.spy() object and pass it as next parameter, but is not called apparently.
Another approach I've tried is to check if exists req.user, since the purpose of the express-jwt middleware is to validate and attach user to the req object. No luck with this neither.
I've seen the existence of chai-connect, but not sure how to use it.
Any ideas? Highly appreciate it!
I finally managed to do it with proxyquire and chai-connect:
In your mocha config:
chai.use(require('chai-connect-middleware'));
global.connect = chai.connect;
In your test:
describe('isAuthenticated', () => {
// Wrap call to chai.connect into a function to use params and return a Promise
const mockedMiddleware = (changeSecret) => {
let oldToken;
if (changeSecret) {
oldToken = acessToken;
acessToken = 'blabalblalba';
}
return new Promise((resolve, reject) => {
connect.use(middleware.isAuthenticated)
.req(req => {
req.query = { access_token: acessToken };
})
.next((res) => {
acessToken = oldToken;
if (res && res.status === 401) {
reject(res.message);
} else {
resolve();
}
})
.dispatch();
});
};
it('should validate correctly', () =>
mockedMiddleware().should.be.fulfilled
);
it('should not validate', () =>
mockedMiddleware(true).should.be.rejected
);
});