How to mock functions when testing with jest and superagent - node.js

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

Related

TypeError: getUserInfo is not a function

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

How to unit test controllers in node.js applications?

I am using fastify framework for my node.js application and sequelize as an ORM. I am using mocha, chai, and Sinon for unit testing. I have to unit test my controller function. The following is the sample controller function.
// controllers.js;
const service = require('../services');
exports.create = (req, reply) => {
const attributes = req.body;
service.create(attributes)
.then((result) => {
reply.code(201).send(result);
})
.catch((error) => {
reply.send(error);
});
};
and my services file is as follows,
// services.js;
const { Model } = require('../models');
function create(attributes) {
return Model.create(attributes);
}
module.exports = { create };
In the above code, I want to unit test only the 'create' function in controllers.js. The problem is, it should not call the database, since it is unit testing. But the Model.create in service.js file will make a call to the database. How can I unit test controller function only?
You should stub service.create method and create mocked req, reply objects.
E.g.
controller.js:
const service = require('./service');
exports.create = (req, reply) => {
const attributes = req.body;
service
.create(attributes)
.then((result) => {
reply.code(201).send(result);
})
.catch((error) => {
reply.send(error);
});
};
service.js:
const { Model } = require('./models');
function create(attributes) {
return Model.create(attributes);
}
module.exports = { create };
models.js:
const Model = {
create() {
console.log('real implementation');
},
};
module.exports = { Model };
controller.test.js:
const controller = require('./controller');
const service = require('./service');
const sinon = require('sinon');
const flushPromises = () => new Promise(setImmediate);
describe('62536251', () => {
afterEach(() => {
sinon.restore();
});
it('should create', async () => {
const mResult = 'success';
sinon.stub(service, 'create').resolves(mResult);
const mReq = { body: {} };
const mReply = { code: sinon.stub().returnsThis(), send: sinon.stub() };
controller.create(mReq, mReply);
await flushPromises();
sinon.assert.calledWith(mReply.code, 201);
sinon.assert.calledWith(mReply.send, 'success');
});
it('should handle error', async () => {
const mError = new Error('network');
sinon.stub(service, 'create').rejects(mError);
const mReq = { body: {} };
const mReply = { code: sinon.stub().returnsThis(), send: sinon.stub() };
controller.create(mReq, mReply);
await flushPromises();
sinon.assert.calledWith(mReply.send, mError);
});
});
unit test result with coverage report:
62536251
✓ should create
✓ should handle error
2 passing (13ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 83.33 | 100 | 60 | 83.33 |
controller.js | 100 | 100 | 100 | 100 |
models.js | 66.67 | 100 | 0 | 66.67 | 3
service.js | 66.67 | 100 | 0 | 66.67 | 4
---------------|---------|----------|---------|---------|-------------------

how to mock configurable middleware in node js with sinon and mocha

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

Mock function under router in Node js

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

Checking function calling inside POST request handler

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

Resources