Mock method and call through original callback - node.js

I have a method that I am trying to test that is invoked through a callback like so:
sessionService.getSession(req, res, req.query.state, handleSessionResponse);
Is there a way, using Sinon, to mock the getSession method and call through to the original callback (handleSessionResponse) in my function? In other words, I do not care about the implementation of getSession but I would like to delegate to the handleSessionResponse anyway to test it.
Here is a sample of the file:
handleSessionResponse(err, data, response) {
// get to this point and make assertions
}
sessionService.getSession(req, res, req.query.state, handleSessionResponse);

Give this a try.
const sinon = require('sinon')
const { mockReq, mockRes } = require('sinon-express-mock')
let request = {
query:{
state:{}
}
}
const req = mockReq(request)
const res = mockRes()
sinon.stub(sessionService, 'getSession').callsFake((req, res, req.query.state, callback) => {
callback(null, {}, {})
});
callback should call your original handleSessionResponse

Related

Proper Jest Testing Azure Functions

I am wondering how to properly test Azure Functions with Jest. I have read the online documentation provided by MSoft but it's very vague, and brief. There are also some outdated articles I found that don't really explain much. Here is what I understand: I understand how to test normal JS async functions with Jest. And I understand how to test very simple Azure Functions. However I am not sure how to go about properly testing more complex Azure Functions that make multiple API calls, etc.
For example I have an HTTP Function that is supposed to make a few API calls and mutate the data and then return the output. How do I properly mock the API calls in the test? We only have one point of entry for the function. (Meaning one function that is exported module.exports = async function(context,req). So all of our tests enter through there. If I have sub functions making calls I can't access them from the test. So is there some clever way of mocking the API calls? (since actually calling API's during tests is bad practice/design)
Here is a sample of code to show what I mean
module.exports = async function (context, req)
{
let response = {}
if (req.body && req.body.id)
{
try
{
//get order details
response = await getOrder(context, req)
}
catch (err)
{
response = await catchError(context, err);
}
}
else
{
response.status = 400
response.message = 'Missing Payload'
}
//respond
context.res =
{
headers: { 'Content-Type': 'application/json' },
status: response.status,
body: response
}
};
async function getOrder(context, req)
{
//connection to db
let db = await getDb() // <- how to mock this
//retrieve resource
let item = await db.get...(id:req.body.id)... // <- and this
//return
return {'status':200, 'data':item}
}
Consider this (simplified) example.
src/index.js (Azure Function entry point):
const { getInstance } = require('./db')
module.exports = async function (context) {
// assuming we want to mock getInstance and db.getOrder
const db = await getInstance()
const order = await db.getOrder()
return order
}
src/db.js:
let db
async function getInstance() {
if (db === undefined) {
// connect ...
db = new Database()
}
return db
}
class Database {
async getOrder() {
return 'result from real call'
}
}
module.exports = {
getInstance,
Database,
}
src/__tests__/index.test.js:
const handler = require('../index')
const db = require('../db')
jest.mock('../db')
describe('azure function handler', () => {
it('should call mocked getOrder', async () => {
const dbInstanceMock = new db.Database() // db.Database is already auto-mocked
dbInstanceMock.getOrder.mockResolvedValue('result from mock call')
db.getInstance.mockResolvedValue(dbInstanceMock)
const fakeAzureContext = {} // fake the context accordingly so that it triggers "getOrder" in the handler
const res = await handler(fakeAzureContext)
expect(db.getInstance).toHaveBeenCalledTimes(1)
expect(dbInstanceMock.getOrder).toHaveBeenCalledTimes(1)
expect(res).toEqual('result from mock call')
})
})
> jest --runInBand --verbose
PASS src/__tests__/index.test.js
azure function handler
✓ should call mocked getOrder (4 ms)
For a complete quickstart, you may want to check my blog post

Jest Express testing middleware with arguments

I'm pretty new to node and this is my first time unit testing an app. I'm doing well with Jest faking the request with Jest function as below
// Create a fake request
const mockRequest = (sessionData, body) => ({
session: { data: sessionData },
body
});
// Create a fake response
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const mockNext = () => {
const next = jest.fn();
return next;
};
So I can use them like follows
doSomething(req, res, next);
expect(res.status).toHaveBeenCalledWith(201);
//or
expect(next).toHaveBeenCalled();
That's enough for all the cases until I found that my authorisation middleware includes a couple of parameters so I can not pass the fake res and req as below
exports.isAllowedTo = (par1, par2) => {
return async (req, res, next) => {
try {
//
// Grant logic here that needs par1 and par2
//
if(granted)
next();
else
return res.status(401).json({
error: "You don't have enough permission to perform this action"
});
} catch (err) {
res.status(406).json({
error: err.toString(),
})
}
}
}
If I test isAllowTo(req, res, next) with the mock req, res and next then I'm missing the 2 parameters needed by the function. Actually when I do this, the function isAllowTo() is not even called. I don't know how to deal with that. Any suggestion or approach?
Two months later I realized that the real problem is that I'm testing a function inside of another function.
So firstly I store the function in a variable so I can test it as a regular middleware.
test('Grant access if user role is allowed to', async () => {
const isAllowToTester = userController.isAllowedTo(par1, par2);
await isAllowToTester(req, res, next)
expect(next).toHaveBeenCalled();
});
Hope this helps someone else.
Credits to this post
Check out https://github.com/nock/nock it's a library dedicated to mocking requests and responses, it's really easy to use with unit tests/jest. I personally don't think is worth it to write your own mocking implementation.

Mocha, Sinon and Chai testing two http calls inside a callback

I am doing some really simple testing with Chai Mocha and Sinon. I am wondering how you would go about testing a http method that gets called inside of a callback. Please point me in the right direction if you can, struggling to find anything on it, I know you can do it, just not sure how. My code is below:
index.js
const http = require('http')
class Index {
add(a, b) {
return a + b
}
get(uri) {
http.get(uri, () => {
http.get('/', function() {
return
})
})
}
}
module.exports = Index
index.spec.js
const Index = require('../index')
const http = require('http')
const { stub, fake, assert } = require('sinon')
const { expect } = require('chai')
let httpSpy;
beforeEach(function () {
a = new Index()
httpSpy = stub(http, 'get')
})
describe('When the get method is invoked', function () {
beforeEach(function () {
a.get('http://www.google.co.uk')
})
it('should make a call to the http service with passed in uri', function () {
assert.calledOnce(httpSpy) // This really should be called twice (Part I am struggling with)
assert.calledWith(httpSpy, 'http://www.google.co.uk')
// I want to test instead that the httpSpy was called twice as, inside the get method, when the first http get resolves, another one gets fired off also
})
})
There are two issues.
Firstly, we cannot tell when the Index.get() method execution is ended (it does not accept a callback, neither return a promise, not marked as async etc).
get(uri) { ... }
Usage of such method is critically inconvenient. For example: if we'd want to first do Index.get() and then do some action right after we won't be able to.
To fix the this we can just add a callback as a last parameter of Index.get.
The second issue is how the http.get method is stubbed:
httpSpy = stub(http, 'get')
This line basically means: replace http.get with an empty function. That dummy function won't throw if you pass a callback inside but it won't call it.
That is why http.get is called only once. It simply ignores the passed callback where http.get should be called the second time.
To fix this we can use stub.yields() method to make sinon aware that the last parameter passed to a stub is a callback (and sinon needs to call it). You can find the method in the docs.
Here is a working example, please see my comments:
class Index {
// Added a callback here
get(uri, callback) {
http.get(uri, () => {
http.get('/', () => {
// Pass any data you want to return here
callback(null, {});
})
})
}
}
let httpSpy;
beforeEach(() => {
a = new Index()
// Now sinon will expect a callback as a last parameter and will call it
httpSpy = stub(http, 'get').yields();
})
describe('When the get method is invoked', () => {
const uri = 'http://www.google.co.uk';
// Now we are waiting for the execution to end before any assertions
beforeEach(done => {
a.get(uri, done);
});
it('should make a call to the http service with passed in uri', () => {
assert.calledTwice(httpSpy);
assert.match(httpSpy.getCall(0).args[0], uri);
assert.match(httpSpy.getCall(1).args[0], '/');
});
})

How to test express middleware with event emitters

I'm trying to write a unit test for an express middleware with event emitters using http-proxy, sinon & rewire. The issue I'm running into is when I stub the external module (in this case http-proxy) when it reaches the event.on line it fails because its undefined.
Heres a snippet of source code:
let middleware = function(options) {
proxy.__proto__ = middleware;
proxy.proxy = buildProxy(extend({}, DEFAULT_OPTIONS, options));
_options = options || {};
proxy.proxy.on('error', function(e, req, res) { // <--- Cannot read property of undefined
logger(req, res, e);
});
proxy.proxy.on('proxyRes', function(proxyRes, req, res) {
logger(req, proxyRes);
});
return proxy;
};
// This method is stubbed
let buildProxy = function(options) {
return httpProxy.createProxyServer(options); // http-proxy module
};
Snippet of the test code:
it.only('should call buildProxy and pass along options', () => {
const testOptions = { someOption: true }
let applicationUnderTest = rewire(UNDER_TEST);
let methodUnderTest = applicationUnderTest.__get__('middleware');
let buildProxySpy = sinon.spy();
applicationUnderTest.__set__('buildProxy', buildProxySpy);
methodUnderTest(testOptions)
expect(buildProxySpy.calledOnce).to.be.true;
})
I was looking for suggestions on how to work around this issue.
Test results:
1) Proxy legacy/integration tests should call buildProxy and pass along options:
TypeError: Cannot read property 'on' of undefined
You need to instruct your spy to return something when called. With sinon, this will actually be a stub:
...
let buildProxySpy = sinon.stub();
buildProxySpy.returns({
proxy: { on: sinon.spy() }
});
...
And of course, you'll want to write additional tests that verify the calls to that returned objects methods, and tests to verify the behavior of the methods passed to the spy returned by the stub.

Unit testing validation with express-validator

How can I unit test my validations that are done using express-validator?
I have tried creating a dummy request object, but I get the error: TypeError: Object #<Object> has no method 'checkBody'. I am able to manually test that the validation works in the application.
Here is what I have tried:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
My understanding is that the checkBody method is added to the request object when I have app.use(expressValidator()) in my express application, but as I am only testing that the validation is working in this unit test I do not have an instance of the express application available, and the validation method that I am testing is not called directly from it anyway as it is only called through a post route, which I do not want to call for a unit test as it involves a database operation.
Here's a solution for the new express-validator api (v4):
tl;dr: You can use this function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
It can be called like this:
const { validationResult } = require('express-validator/check');
await testExpressValidatorMiddleware(req, res, expressValidatorMiddlewareArray);
const result = validationResult(req);
expect(result....
These solutions assume you have the async/await syntax available. You can use the node-mocks-http library to create the req and res objects.
Explanation:
Each element in an express-validator array is applied to the route as middleware. Say this is your array:
[
check('addresses.*.street').exists(),
check('addresses.*.postalCode').isPostalCode(),
]
Each check will be loaded as middleware.
In order to test middleware, we need to implement a function which acts similarly to how express implements middleware.
Express middleware always accepts three params, the request and response objects, and the next function it should call (next by convention). Why do we need next? For scenarios where we want our middleware to do something before and after the proceeding function, e.g.
const loggerMiddleware = (req, res, next) => {
console.log('req body is ' + req.body);
next();
console.log('res status is ' + res.status);
};
But express-validator doesn't do this, it just calls next() once each of its validators is finished. For that reason, our implementation doesn't really need to bother with next().
Instead, we can just run each of our middlewares in turn and pass an empty function as next to avoid a TypeError:
middlewares.map((middleware) => {
middleware(req, res, () => undefined);
});
But this won't work, because express-validator middleware returns promises and we need to wait for them to resolve...
middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
});
And we don't want to move on until all promises in our iteration are resolved (Mozilla docs on Promise.all are here):
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
And we should extract this as a reusable function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
And now we've arrived at my solution. If someone can improve on this implementation, I'm very happy to make edits.
I faced the same issue and I had to create the methods using this:
var validRequest = {
// Default validations used
checkBody: function () { return this; },
checkQuery: function () { return this; },
notEmpty: function () { return this; },
// Custom validations used
isArray: function () { return this; },
gte: function () { return this; },
// Validation errors
validationErrors: function () { return false; }
};
function getValidInputRequest(request) {
Object.assign(request, validRequest);
return request;
}
So, in your code you have to call the getValidInputRequest helper:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
request = getValidInputRequest(request); // <-- Update the request
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
Now, the request object has the body property and all the methods needed by express-validator.
If you want to test the cases that the validator fails, you should use something like this:
function getInvalidInputRequest(request, errorParams) {
// Get de default valid request
Object.assign(request, validRequest);
// Override the validationErrors function with desired errors
request.validationErrors = function () {
var errors = [];
errorParams.forEach(function(error){
errors.push({msg: 'the parameter "'+ error +'" is mandatory'})
});
return errors;
};
return request;
}
And to update the request you should do:
request = getInvalidInputRequest(request, ['mandatory_param_1', 'mandatory_param_2']);

Resources