How do I individually implement stubs to functions invoked inside a parent function?
Assuming I have these functions (req1,req2...) that are HTTP Requests from external services that are all returning differing values, is there a way where I can apply stubs for req1 or req2 individually to mock their values?
The purpose of this is because I need to do this to test a function that relies on an OTP verification and I want to bypass said verification in order to cover all branches in my testing.
import request from 'request-promise'
const request1 = async (data) => return request({uri: "service1.com/get", method: "GET"})
const apiRequests = async (data) => {
const req1 = await request1(data); // I want to mock this value to false
const req2 = await request2(data); // I want to mock this value to true
if (req1 && req2) {
const req3 = await request3(data);
const req4 = await request4(data);
return "Second return"
}
return "First return"
}
I've always been overwhelmed whenever trying to understand the deeper levels of mocking and most of the examples I see online aren't as nested the problem I'm facing so I'm a bit puzzled about how to go on about this.
I also work in a pretty strict setup so I'm not really allowed to use any other libraries/packages outside of Loopback's built-in testing libraries.
You can use stub.onCall(n) API.
Defines the behavior of the stub on the nth call. Useful for testing sequential interactions.
Besides, sinon does NOT support stub a standalone function import from a package, you need to use link seams, so that we use proxyquire package to construct seams.
E.g.
apiRequest.ts:
import request from 'request-promise';
const request1 = async (data) => request({ uri: 'service1.com/get', method: 'GET' });
export const apiRequests = async (data) => {
const req1 = await request1(data);
const req2 = await request1(data);
console.log(req1, req2);
if (req1 && req2) {
const req3 = await request1(data);
const req4 = await request1(data);
return 'Second return';
}
return 'First return';
};
apiRequest.test.ts
import proxyquire from 'proxyquire';
import sinon from 'sinon';
describe('70241641', () => {
it('should second return', async () => {
const rpStub = sinon.stub().onCall(0).resolves(true).onCall(1).resolves(true);
const { apiRequests } = proxyquire('./apiRequest', {
'request-promise': rpStub,
});
const actual = await apiRequests('test data');
sinon.assert.match(actual, 'Second return');
});
it('should first second', async () => {
const rpStub = sinon.stub().onCall(0).resolves(false).onCall(1).resolves(true);
const { apiRequests } = proxyquire('./apiRequest', {
'request-promise': rpStub,
});
const actual = await apiRequests('test data');
sinon.assert.match(actual, 'First return');
});
});
test result:
70241641
true true
✓ should second return (2374ms)
false true
✓ should first second
2 passing (2s)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
apiRequest.ts | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
Related
I am currently working on a full-stack application using the MERN stack. I have been writing unit tests for each route's request handler, and have been finding some difficulty testing error cases, particularly in trying to stub a function to reject a promise
I have the relevant code shown below:
One of my endpoints. Request handling is delegated to userController
const express = require("express");
const { body } = require("express-validator");
const router = express.Router();
const userController = require("../../controllers/user");
router.post(
"/",
body("username")
.isLength({
min: 3,
max: 30,
})
.withMessage(
"Your username must be at least 3 characters and no more than 30!"
),
body("password")
.isLength({ min: 3, max: 50 })
.withMessage(
"Your password must be at least 3 characters and no more than 50!"
),
userController.createNewUser
);
The request handler for the above endpoint. I am trying to test createNewUser. I want to stub createNewUser so that it causes an error to be thrown, so I can test that a 500 status code response is sent.
const bcrypt = require("bcryptjs");
const { validationResult } = require("express-validator");
const User = require("../models/User");
exports.createNewUser = async (req, res, next) => {
const { username, password } = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array(),
});
}
try {
// Create a bcrypt salt
const salt = await bcrypt.genSalt(12);
// Hash the password
const hashedPassword = await bcrypt.hash(password, salt);
// Create a new user
const user = new User({
username,
password: hashedPassword,
});
const response = await user.save();
res.status(200).json(response);
} catch (err) {
res.status(500).json({ msg: err.message });
}
};
The unit test for User endpoints. I am unsure how to test the error case where a 500 status code is returned...
const request = require("supertest");
// const todosController = require("../controllers/todos");
const server = require("../server");
const User = require("../models/TodoItem");
const db = require("./db");
const agent = request.agent(server);
// Setup connection to the database
beforeAll(async () => await db.connect());
afterEach(async () => await db.clear());
afterAll(async () => await db.close());
describe("User endpoints test suite", () => {
describe("POST api/user", () => {
test("It should create a user successfully and return a 200 response code", async () => {
const response = await agent
.post("/api/user")
.set("content-type", "application/json")
.send({ username: "Bob", password: "12345" });
expect(response.body.username).toEqual("Bob");
expect(response.status).toBe(200);
});
});
});
When you are creating unit test, create something small first, you can add complexity and refactor later.
Below are example simple unit and integration tests based on your code.
You can start with user controller.
// File: user.controller.js
const bcrypt = require('bcryptjs');
exports.createNewUser = async (req, res) => {
try {
// Create a bcrypt salt.
const salt = await bcrypt.genSalt(12);
// Just make it simple, show the salt.
res.status(200).json(salt);
} catch (err) {
// Other wise, return the error message.
res.status(500).json({ msg: err.message });
}
};
Based on that try and catch, you can create unit test.
// File: user.controller.spec.js
const bcrypt = require('bcryptjs');
const user = require('./user.controller');
describe('User Controller', () => {
describe('create New User', () => {
const fakeJson = jest.fn();
const fakeStatus = jest.fn().mockReturnThis();
const fakeRes = {
status: fakeStatus,
json: fakeJson,
};
const spy = jest.spyOn(bcrypt, 'genSalt');
afterEach(() => {
jest.clearAllMocks();
});
it('should return salt', async () => {
const testSalt = 'salt';
// Mock the bcrypt.genSalt, always resolved with value testSalt.
spy.mockResolvedValue(testSalt);
// Call the function under test.
await user.createNewUser(undefined, fakeRes);
// Set the expectations.
expect(fakeStatus).toHaveBeenCalledWith(200);
expect(fakeJson).toHaveBeenCalledWith(testSalt);
expect(spy.mock.calls[0][0]).toBe(12);
});
it('should return error message when error', async () => {
const error = new Error('XXX');
// Mock the bcrypt.genSalt, always resolved with value testSalt.
spy.mockRejectedValue(error);
// Call the function under test.
await user.createNewUser(undefined, fakeRes);
// Set the expectations.
expect(fakeStatus).toHaveBeenCalledWith(500);
expect(fakeJson).toHaveBeenCalledWith({ msg: error.message });
expect(spy.mock.calls[0][0]).toBe(12);
});
});
});
When you run it on terminal:
$ npx jest user.controller.spec.js
PASS ./user.controller.spec.js
User Controller
create New User
✓ should return salt (5 ms)
✓ should return error message when error (1 ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
user.controller.js | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.511 s, estimated 1 s
Ran all test suites matching /user.controller.spec.js/i.
Next, if you have sure with your controller, you can create integration test with express.
For example you create app index like this.
// File: index.js
const express = require('express');
const userController = require('./user.controller');
const router = express.Router();
router.post('/user', (req, res, next) => userController.createNewUser(req, res, next));
const app = express();
app.use('/api', router);
module.exports = app;
You can test it using jest for normal & error case like this.
// File: index.spec.js
const request = require('supertest');
const bcrypt = require('bcryptjs');
const server = require('./index');
const userController = require('./user.controller');
const agent = request.agent(server);
describe('App', () => {
describe('POST /', () => {
// Create spy on bcrypt.
const spy = jest.spyOn(bcrypt, 'genSalt');
const error = new Error('XXX');
afterEach(() => {
jest.clearAllMocks();
});
it('should create a salt successfully and return a 200 response code', async () => {
// This test is slow because directly call bcrypt.genSalt.
// To make it faster, mock bcrypt completely, or use spy.mockResolvedValue('SALT');
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(200);
expect(response.type).toBe('application/json');
expect(spy.mock.results[0].value).toBeDefined();
const spyResult = await spy.mock.results[0].value;
expect(response.body).toBe(spyResult)
});
it('should return 500 and error message when catch error', async () => {
// Makesure spy reject.
spy.mockRejectedValue(error);
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(500);
expect(response.type).toBe('application/json');
expect(response.body).toBeDefined();
expect(response.body.msg).toBeDefined();
expect(response.body.msg).toBe(error.message);
});
// Or play around with another spy to error alternatives.
it('should return 404 when pass to next', async () => {
// Makesure createNewUser error.
jest.spyOn(userController, 'createNewUser').mockImplementation((req, res, next) => {
// You can setup res here or other implementation to check.
// For example, do next.
next();
});
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(404);
// Method bcrypt.genSalt should not get called.
expect(spy).not.toHaveBeenCalled();
});
});
});
When you run it from terminal:
$ npx jest index.spec.js
PASS ./index.spec.js
App
POST /
✓ should create a salt successfully and return a 200 response code (40 ms)
✓ should return 500 and error message when catch error (4 ms)
✓ should return 404 when pass to next (5 ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
user.controller.js | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.809 s, estimated 1 s
Ran all test suites matching /index.spec.js/i.
Note: You do not need to use sinon, jest provides mock functions.
I'm new to writing mocha unit tests and would like to ask how the unit test for the following code would look like (especially for the BigQuery part). The code is in a PubSub triggered Cloud Function and inserts rows into a BigQuery table:
/**
* Triggered from a message on a Cloud Pub/Sub topic.
*
* #param {!Object} event Event payload.
* #param {!Object} context Metadata for the event.
*/
const {BigQuery} = require('#google-cloud/bigquery');
const bigqueryClient = new BigQuery();
const dataset = 'dataset_name';
const table = 'table_name';
exports.sendtobigquery = (event, context) => {
const pubsubMessage = Buffer.from(event.data, 'base64').toString();
BigQueryInsert(pubsubMessage, dataset, table);
};
async function BigQueryInsert(pubsubMessage, dataset, table) {
const date = new Date().toISOString(); // new date
const rows = [{field1: pubsubMessage, field2: date}]; // new field2
await bigqueryClient
.dataset(dataset)
.table(table)
.insert(rows);
}
I've seen here Mocking ES6 BigQuery class that sinon and proxyquire should be used but I don't understand how to do that for my code exactly.
Any help is appreciated.
Update:
I added a new date field as part of the row but I'm having trouble here too when it comes to the unit test. For the date I tried using sinon.useFakeTimers in the index.test.js from the answer like that
describe('66267929', () => {
const now = new Date();
const date = sinon.useFakeTimers(now.getTime());
beforeEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
... // same code
... // same code
sinon.assert.calledWithExactly(bigqueryClientStub.insert, [{ field1: 'teresa teng', field2: date }]);
});
});
but that resulted in "AssertError: expected stub to be called once but was called 0 times". How can this be done?
Since you call the BigQueryInsert function without async/await, we need to flush the promise queue to ensure that all asynchronous method calls have been completed on the bigqueryClient object.
We use proxyquire and sinonjs to stub the BigQuery constructor.
We use returnsThis() to achieve the chain methods call.
E.g.
index.js:
const { BigQuery } = require('#google-cloud/bigquery');
const bigqueryClient = new BigQuery();
const dataset = 'dataset_name';
const table = 'table_name';
exports.sendtobigquery = (event, context) => {
const pubsubMessage = Buffer.from(event.data, 'base64').toString();
BigQueryInsert(pubsubMessage, dataset, table);
};
async function BigQueryInsert(pubsubMessage, dataset, table) {
const rows = [{ field1: pubsubMessage }];
await bigqueryClient.dataset(dataset).table(table).insert(rows);
}
index.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
describe('66267929', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const bigqueryClientStub = {
dataset: sinon.stub().returnsThis(),
table: sinon.stub().returnsThis(),
insert: sinon.stub().resolves(),
};
const googleCloundBigqueryStub = {
BigQuery: sinon.stub().returns(bigqueryClientStub),
};
const { sendtobigquery } = proxyquire('./', {
'#google-cloud/bigquery': googleCloundBigqueryStub,
});
const data = Buffer.from('teresa teng').toString('base64');
sendtobigquery({ data });
await flushPromises();
sinon.assert.calledOnce(googleCloundBigqueryStub.BigQuery);
sinon.assert.calledWithExactly(bigqueryClientStub.dataset, 'dataset_name');
sinon.assert.calledWithExactly(bigqueryClientStub.table, 'table_name');
sinon.assert.calledWithExactly(bigqueryClientStub.insert, [{ field1: 'teresa teng' }]);
});
});
unit test result:
66267929
✓ should pass (343ms)
1 passing (346ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
I am trying to stub a call to the aws parameter store (PS). But even though I added the stub in multiple ways it always makes the actual call to aws PS.
the method I am trying to test
function getParamsFromParamterStore() {
return ssm.getParametersByPath(query).promise();
}
One of the stub method I tried
var ssm = new AWS.SSM();
stub1 = sinon.stub(ssm, 'getParametersByPath').returns({promise: () => {}});
moduleName.__get__('getParamsFromParamterStore')();
But this actually makes the call to PS.
Note: since this is a private function (not exported) I am using rewire to access it.
Here is the unit test solution:
index.js:
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();
function getParamsFromParamterStore(query) {
return ssm.getParametersByPath(query).promise();
}
index.test.js:
const rewire = require('rewire');
const sinon = require('sinon');
const { expect } = require('chai');
const mod = rewire('./');
describe('60447015', () => {
it('should pass', async () => {
const ssmMock = { getParametersByPath: sinon.stub().returnsThis(), promise: sinon.stub().resolves('mock data') };
const awsMock = {
SSM: ssmMock,
};
mod.__set__('ssm', awsMock.SSM);
const actual = await mod.__get__('getParamsFromParamterStore')('query');
expect(actual).to.be.eq('mock data');
sinon.assert.calledWithExactly(ssmMock.getParametersByPath, 'query');
sinon.assert.calledOnce(ssmMock.promise);
});
});
Unit test results with 100% coverage:
60447015
✓ should pass
1 passing (30ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
I'm using the package aws-param-store and I'm trying to write Unit tests
that stub calls to getParametersByPath().
Package can be found here:
https://www.npmjs.com/package/aws-param-store
Here is my sinon code to stub the call. The function getParametersByPath is an async function
so I'm trying to return a resolved promise to stub it:
const awsParameterStore = require('aws-param-store');
const sinon = require("sinon");
let sandbox = sinon.createSandbox();
// In My Test:
let parms = new Map();
parms.set("key1","value1");
parms.set("key2","value2");
sandbox.stub(awsParameterStore,'getParametersByPath').callsFake(async function(prefix){
console.log("INSIDE STUB for getParametersByPath:" + prefix);
return Promise.resolve(parms);
});
My app makes a call to the function like this:
let parameters = await awsParameterStore.getParametersByPath("/foo");
However, instead of getting back the Map of dummy parameters, I get an empty object {}.
I can see that the stub is getting called.
Any ideas on how to properly stub this so I can return some dummy parameters in my unit tests?
Thanks!
I tried to check your code, and it's completely fine.
import test from 'ava'
const awsParameterStore = require('aws-param-store');
const sinon = require('sinon')
test('test stub', async t => {
let parms = new Map();
parms.set("key1","value1");
parms.set("key2","value2");
parms.set("key3","value3");
parms.set("key4","value4");
sinon.stub(awsParameterStore, 'getParametersByPath').callsFake(async function(prefix){
console.log("INSIDE STUB for getParametersByPath:" + prefix)
return Promise.resolve(parms);
})
const res = await awsParameterStore.getParametersByPath('/some-prefix')
console.log(JSON.stringify(res))
res.forEach((value, key) => {
console.log(`[${key}]= ${value}`)
})
t.true(true)
})
What is weir:
console.log - show that result is {}
but if you debug or log the value one by one, you can see that stub works fine:
Debugger attached.
INSIDE STUB for getParametersByPath:/some-prefix
{}
[key1]= value1
[key2]= value2
[key3]= value3
[key4]= value4
✔ test stub
UPD: problem is how to log Map by JSON.stringify(). So, you must log the map as:
console.log(JSON.stringify([...res]))
Here is the unit test solution:
index.js:
const awsParameterStore = require('aws-param-store');
async function main() {
let parameters = await awsParameterStore.getParametersByPath('/foo');
return parameters;
}
module.exports = main;
index.test.js:
const main = require('./');
const sinon = require('sinon');
const awsParameterStore = require('aws-param-store');
const { expect } = require('chai');
let sandbox = sinon.createSandbox();
describe('59787603', () => {
it('should pass', async () => {
let parms = new Map();
parms.set('key1', 'value1');
parms.set('key2', 'value2');
sandbox.stub(awsParameterStore, 'getParametersByPath').resolves(parms);
const actual = await main();
expect(actual).to.be.eql(parms);
});
});
Unit test results with 100% coverage:
59787603
✓ should pass
1 passing (11ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/59787603
What would be the proper way to unit test this getall function using mocha/chai? I'm having a hard time understanding what to expect with Promise.all.
const Promise = require('bluebird');
const someApiService = require('./someapiservice');
const _ = require('underscore');
function getall(arr) {
let promises = _.map(arr, function(item) {
return someApiService(item.id);
});
return Promise.all(promises);
}
We should stub standalone function someApiService use link seams. This is the CommonJS version, so we will be using proxyquire to construct our seams. Additionally, I use sinon.js to create the stub for it.
E.g.
getall.js:
const Promise = require('bluebird');
const someApiService = require('./someapiservice');
const _ = require('underscore');
function getall(arr) {
let promises = _.map(arr, function (item) {
return someApiService(item.id);
});
return Promise.all(promises);
}
module.exports = getall;
someapiservice.js:
module.exports = function someApiService(id) {
return Promise.resolve('real data');
};
getall.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const { expect } = require('chai');
describe('45337461', () => {
it('should pass', async () => {
const someapiserviceStub = sinon.stub().callsFake((id) => {
return Promise.resolve(`fake data with id: ${id}`);
});
const getall = proxyquire('./getall', {
'./someapiservice': someapiserviceStub,
});
const actual = await getall([{ id: 1 }, { id: 2 }, { id: 3 }]);
expect(actual).to.deep.equal(['fake data with id: 1', 'fake data with id: 2', 'fake data with id: 3']);
sinon.assert.calledThrice(someapiserviceStub);
});
});
unit test result:
45337461
✓ should pass (2745ms)
1 passing (3s)
-------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------|---------|----------|---------|---------|-------------------
All files | 88.89 | 100 | 66.67 | 88.89 |
getall.js | 100 | 100 | 100 | 100 |
someapiservice.js | 50 | 100 | 0 | 50 | 2
-------------------|---------|----------|---------|---------|-------------------