I have configurable middleware where I can pass parameters and based on that it calls next function.
middleware code:
File: my-middleware.js
exports.authUser = function (options) {
return function (req, res, next) {
// Implement the middleware function based on the options object
next()
}
}
var mw = require('./my-middleware.js')
app.use(mw.authUser({ option1: '1', option2: '2' }))
How to mock the middleware using sinon js?
I have done in this way but, it throwing me "TypeError: next is not a function".
Here is my unit test code:
it("Should return data by id", (done: any) => {
sandbox.stub(mw, 'authUser')
.callsFake((req: any, res: any, next: any) => { return next(); });
server = require('../../index');
let req = {
"id": '123'
}
chai.request(server)
.post("/user")
.send(req)
.end((request, res) => {
expect(res.status).to.equal(200);
expect(res.body.success).to.equal(true);
done();
});
});
Can you help me to mock the configurable middleware? Thanks in advance!!
The authUser is a high order function, so you need to stub it for this.
E.g.
mws.js:
exports.authUser = function(options) {
return function(req, res, next) {
// Implement the middleware function based on the options object
next();
};
};
app.js:
const express = require('express');
const mws = require('./mws');
const app = express();
app.use(mws.authUser({ option1: '1', option2: '2' }));
app.post('/user', (req, res, next) => {
res.json({ success: true });
});
module.exports = app;
app.integration.test.js:
const chai = require('chai');
const chaiHttp = require('chai-http');
const sandbox = require('sinon').createSandbox();
const mws = require('./mws');
chai.use(chaiHttp);
const expect = chai.expect;
describe('61818474', () => {
afterEach(() => {
sandbox.restore();
});
it('Should return data by id', (done) => {
sandbox.stub(mws, 'authUser').callsFake((options) => (req, res, next) => {
return next();
});
const server = require('./app');
const req = { id: '123' };
chai
.request(server)
.post('/user')
.send(req)
.end((request, res) => {
expect(res.status).to.equal(200);
expect(res.body.success).to.equal(true);
sandbox.assert.calledWith(mws.authUser, { option1: '1', option2: '2' });
done();
});
});
});
integration test results with coverage report:
61818474
✓ Should return data by id (257ms)
1 passing (265ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 80 | 100 | 33.33 | 80 |
app.js | 100 | 100 | 100 | 100 |
mws.js | 33.33 | 100 | 0 | 33.33 | 2-4
----------|---------|----------|---------|---------|-------------------
By specifying jestjs in your tags... I guess you might want to use additional functions that Jest gives you for free.
You don't really need Sinon in this case, Jest has its own way to mock/stub (entire modules too), something like jest.mock('moduleName').
You might want to check the following link for useful examples and possible usages:
https://jestjs.io/docs/en/manual-mocks
Related
I am using Jest for unit testing my code.
I have a file userinfo.js
const getuserdata = async function (req, res, next) {
let userinfo = await getUserInfo();
return userinfo;
}
const getUserInfo = async function (req, res, next) {
const records = await database.query('select * from users');
return records;
}
module.exports = {
getuserdata,
getUserInfo
}
I want to write test in jest
I created a file userinfo.test.js for testing.
code:
const { getuserdata, getUserInfo } = require('./userinfo');
describe('user data', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('user info', async () => {
jest.mock('Path of file', () => ({ getUserInfo: jest.fn() }));
getUserInfo.mockResolvedValue([{id: 1, name: "test", password: "123456"}]);
const userdata = await getuserdata();
expect(userdata).not.toBeUndefined();
});
}
I want to test getuserdata but mock getUserInfo.
When I try to mock by above method it is giving me error that: TypeError: getUserInfo is not a function
but if I save the getUserInfo in different file then mocking is working properly. can you please suggest me how to fix this issue.
You need to do some refactoring. We need to make sure getUserInfo method has the same reference, then you can add spy on it or replace it with a mocked object. You should NOT use jest.mock in the function scope. It should be used in the module scope. This scenario is more appropriate to use jest.spyOn to mock getUserInfo method and its returned value.
E.g.
userinfo.js:
const database = require('./database');
const getuserdata = async function (req, res, next) {
let userinfo = await exports.getUserInfo();
return userinfo;
};
const getUserInfo = async function (req, res, next) {
const records = await database.query('select * from users');
return records;
};
exports.getuserdata = getuserdata;
exports.getUserInfo = getUserInfo;
database.js:
const database = {
query(sql) {
console.log('real implementation');
},
};
module.exports = database;
userinfo.test.js:
const userInfo = require('./userinfo');
describe('62539321', () => {
it('should pass', async () => {
const getUserInfoSpy = jest
.spyOn(userInfo, 'getUserInfo')
.mockResolvedValue([{ id: 1, name: 'test', password: '123456' }]);
const userdata = await userInfo.getuserdata();
expect(userdata).toEqual([{ id: 1, name: 'test', password: '123456' }]);
getUserInfoSpy.mockRestore();
});
});
unit test result with coverage report:
62539321
✓ should pass (7ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 71.43 | 50 | 33.33 | 75 |
database.js | 66.67 | 100 | 0 | 66.67 | 3
userinfo.js | 72.73 | 50 | 50 | 77.78 | 9-10
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.609s
Please consider the following scenario, but I could not solve it. A class are called under the router("/"). I would like to test router ("/") call and mock myFunc result.
class A {
myFunc = async () => {
await ...
return result
}
}
Controller file - C:
router.get("/", async (req, res) => {
var a = new A();
a.myFunc()
.then(result => {
res.json({"message": result});
}).catch(e => {
return []
})
});
C.test.js:
const request = require("supertest");
const app = require("../C");
jest.mock('A');
test("Test route /", async done => {
const myFuncMock = jest.fn().mockImplementation(() => [...]);
A.prototype.myFunc = myFuncMock;
await request(app)
.get("/")
.then(res => {
expect(res.status).toBe(200); // Not asserted
});
done()
});
I could not success to mock function results under the router.
Here is the integration test solution:
app.js:
const express = require('express');
const { Router } = require('express');
const A = require('./a');
const app = express();
const router = Router();
router.get('/', async (req, res) => {
const a = new A();
a.myFunc()
.then((result) => {
res.json({ message: result });
})
.catch((e) => {
return [];
});
});
app.use(router);
module.exports = app;
a.js:
class A {
myFunc = async () => {
const result = await 'real data';
return result;
};
}
module.exports = A;
app.test.js:
const app = require('./app');
const request = require('supertest');
const A = require('./a');
jest.mock('./a', () => {
const mA = { myFunc: jest.fn() };
return jest.fn(() => mA);
});
describe('61505692', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should pass', () => {
const mA = new A();
mA.myFunc.mockResolvedValueOnce('fake result');
return request(app)
.get('/')
.then((res) => {
expect(res.status).toBe(200);
});
});
});
integration test results with coverage report:
PASS stackoverflow/61505692/app.test.js (11.834s)
61505692
✓ should pass (34ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 92.31 | 100 | 75 | 91.67 |
app.js | 92.31 | 100 | 75 | 91.67 | 16
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.785s
I want to mock specific methods of the "crypto" module of nodejs.
I am testing the end-points in my code using jest framework.
Here is how my end-point code looks like for email-verification:
/* engnr_cntrlr.js */
exports.engineeremail_verifcation = async(req, res) => {
try {
// 3. i want to mock the next statement and return a mock-value to hashedToken variable
const hashedToken = crypto
.createHash('sha256')
.update(req.params.token)
.digest('hex');
const engnr = await Engineer.findOne({
engnrToken : hashedToken
}).orFail(new Error ('engnr not found'));
engnr.emailVerify = true
await engnr.save()
return res.status(202).send({ message: 'email verified' });
} catch (error) {
res.status(400).send({ error: error.message });
}
};
test script :
/* tests/engineer.test.js */
test('engineer verifies his email', async done => {
// 1. i am fetching an engnr using an email id
engnr = await Engineer.findOne({
email: engineerOne.email
});
try {
const response = await request(app)
.put(`/api/engineer/confirm_mail/${engnr.token}`) //2. i am sending a hashed token as a req.params
.send();
expect(response.statusCode).toBe(202);
expect(response.body).toMatchObject({
message: 'email verified'
});
done();
} catch (error) {
done(error);
}
});
The problem i am facing is to mock the crypto implementation in my 'engineeremail_verification' (just below comment no.3). I am using other crypto methods in other parts of my code and I do not want to mock them. I just want to mock this specific implementation of crypto module with a mock value being returned. How do I do that? Thank You for going through my question. Appreciate any sort of help.
You can use jest.spyOn(object, methodName) to mock crypto.createHash separately.
E.g.
app.js:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.put('/api/engineer/confirm_mail/:token', async (req, res) => {
try {
const hashedToken = crypto.createHash('sha256').update(req.params.token).digest('hex');
console.log(hashedToken);
return res.status(202).send({ message: 'email verified' });
} catch (error) {
res.status(400).send({ error: error.message });
}
});
app.put('/example/:token', async (req, res) => {
const hashedToken = crypto.createHash('sha256').update(req.params.token).digest('hex');
console.log(hashedToken);
res.sendStatus(200);
});
module.exports = app;
app.test.js:
const app = require('./app');
const request = require('supertest');
const crypto = require('crypto');
describe('61368162', () => {
it('should verify email', async () => {
const hashMock = {
update: jest.fn().mockReturnThis(),
digest: jest.fn().mockReturnValueOnce('encrypt 123'),
};
const createHashMock = jest.spyOn(crypto, 'createHash').mockImplementationOnce(() => hashMock);
const logSpy = jest.spyOn(console, 'log');
const engnr = { token: '123' };
const response = await request(app).put(`/api/engineer/confirm_mail/${engnr.token}`).send();
expect(createHashMock).toBeCalledWith('sha256');
expect(hashMock.update).toBeCalledWith('123');
expect(hashMock.digest).toBeCalledWith('hex');
expect(logSpy).toBeCalledWith('encrypt 123');
expect(response.statusCode).toBe(202);
expect(response.body).toMatchObject({ message: 'email verified' });
createHashMock.mockRestore();
logSpy.mockRestore();
});
it('should restore crypto methods', async () => {
const logSpy = jest.spyOn(console, 'log');
const response = await request(app).put('/example/123');
expect(jest.isMockFunction(crypto.createHash)).toBeFalsy();
expect(response.statusCode).toBe(200);
expect(logSpy).toBeCalledWith(expect.any(String));
logSpy.mockRestore();
});
});
integration test results with coverage report:
PASS stackoverflow/61368162/app.test.js (13.621s)
61368162
✓ should verify email (44ms)
✓ should restore crypto methods (5ms)
console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
encrypt 123
console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 93.75 | 100 | 100 | 92.86 |
app.js | 93.75 | 100 | 100 | 92.86 | 12
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 15.707s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61368162
I have run into an issue that files and functions cannot be mocked, that is used in a handler of an API call. This call is simulated using superagent.
Here is the code of the test
// users.itest.js
const request = require('superagent');
const get = async url => request
.get(`${process.env.API_URL}${url}`);
describe('endpoint', () => {
it('GET', async () => {
jest.mock('../token-store', () => ({
getToken: jest.fn().mockReturnValue('token'),
}));
const { status, body } = await get('/api/users');
expect(status).toEqual(200);
expect(body).toHaveValidSchema(userSchema);
});
And here is the handler that is called by the '/api/users' endpoint
const someHandler = async (req, res) => {
const token = await tokenStore.getToken();
res.send(token);
};
I tried mocking it like shown, however, I couldn't find a solution.
Thanks.
You should use jest.mock() in the module scope, not function scope.
Here is the integration test solution:
app.js:
const express = require('express');
const tokenStore = require('./token-store');
const app = express();
const someHandler = async (req, res) => {
const token = await tokenStore.getToken();
res.send(token);
};
app.get('/api/users', someHandler);
module.exports = app;
token-store.js:
async function getToken() {
return 'real token';
}
module.exports = {
getToken,
};
users.test.js:
const request = require('superagent');
const app = require('./app');
const port = 3000;
process.env.API_URL = `http://localhost:${port}`;
const get = async (url) => request.get(`${process.env.API_URL}${url}`);
jest.mock('./token-store', () => ({
getToken: jest.fn().mockReturnValue('token'),
}));
describe('endpoint', () => {
let server;
beforeAll((done) => {
server = app.listen(port, () => {
console.info(`HTTP server is listening on http://localhost:${server.address().port}`);
done();
});
});
afterAll((done) => {
server.close(done);
});
it('GET', async () => {
const { status, text } = await get('/api/users');
expect(status).toEqual(200);
expect(text).toBe('token');
});
});
Integration test result with coverage report:
PASS src/stackoverflow/59426030/users.test.js (10.767s)
endpoint
✓ GET (74ms)
console.info src/stackoverflow/59426030/users.test.js:16
HTTP server is listening on http://localhost:3000
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
app.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.254s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59426030
I need to test that my POST /login handler calling validServiceURL function inside itself.
I tried to solve this issue using spy from sinon, but didn't manage it.
POST /login handler:
const login = async (req, res) => {
try {
if (await validServiceURL(req.headers.host)) {
await checkUser(req, res);
} else {
res.redirect("/");
}
} catch (err) {
reditectToHomePageWithError(res, err.message);
}
};
Test code:
const chai = require("chai");
const sinon = require("sinon");
const request = require("supertest");
chai.use(require("sinon-chai"));
const expect = chai.expect;
const app = require("../../app");
const validServiceURL = require("../../lib/validServiceURL");
const sandbox = sinon.createSandbox();
describe("User", function() {
describe("POST login", function() {
it("should call validServiceURL with provided serviceURL", async () => {
let args = { username: user.username, password: password };
let serviceURL = "http://127.0.0.1:3030";
let spyValidServiceURL = sandbox.spy(validServiceURL);
await request(app)
.post("/login")
.type("form")
.send(args)
.set("host", serviceURL);
expect(spyValidServiceURL).to.have.been.calledWith(serviceURL);
sandbox.restore();
});
});
});
When I run this test I get error look like:
AssertionError: expected validServiceURL to have been called with arguments http://127.0.0.1:3030
sinon doesn't support stub/spy a standalone function directly. In your case, the standalone function is validServiceURL. For more info, see this issue.
Here is the solution using proxyquire module:
app.js:
const express = require("express");
const validServiceURL = require("./validServiceURL");
const app = express();
async function checkUser(req, res) {
console.log("checkUser");
}
function reditectToHomePageWithError(res, message) {
console.log(message);
}
const login = async (req, res) => {
try {
if (await validServiceURL(req.headers.host)) {
await checkUser(req, res);
} else {
res.redirect("/");
}
} catch (err) {
reditectToHomePageWithError(res, err.message);
}
};
app.post("/login", login);
module.exports = app;
validServiceURL.js:
async function validServiceURL() {}
module.exports = validServiceURL;
app.test.js:
const chai = require("chai");
const sinon = require("sinon");
const request = require("supertest");
const proxyquire = require("proxyquire");
chai.use(require("sinon-chai"));
const expect = chai.expect;
const sandbox = sinon.createSandbox();
describe("User", function() {
describe("POST login", function() {
it("should call validServiceURL with provided serviceURL", async () => {
let args = { username: "username", password: "password" };
let serviceURL = "http://127.0.0.1:3030";
let spyValidServiceURL = sandbox.spy();
const app = proxyquire("./app", {
"./validServiceURL": spyValidServiceURL,
});
await request(app)
.post("/login")
.type("form")
.send(args)
.set("host", serviceURL);
expect(spyValidServiceURL).to.have.been.calledWith(serviceURL);
sandbox.restore();
});
});
});
Integration test result with coverage:
User
POST login
✓ should call validServiceURL with provided serviceURL (468ms)
1 passing (474ms)
--------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------|----------|----------|----------|----------|-------------------|
All files | 87.1 | 50 | 57.14 | 87.1 | |
app.js | 69.23 | 50 | 33.33 | 69.23 | 6,10,16,21 |
app.test.js | 100 | 100 | 100 | 100 | |
validServiceURL.js | 100 | 100 | 0 | 100 | |
--------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57908094