Supertest routes with mock services - node.js

UPDATE
I updated the code below to reflect my solution. It was rather confusing to figure it out but hopefully it will help someone else too.
I'm trying to figure out how to test my routes. The issue I'm running into is, when I make the GET request my node-googleplaces service calls out to the google api. Is there a way to mock out this service so that I can test my route and just fake the data it returns?
controller.js
'use strict';
var path = require('path'),
GooglePlaces = require('node-googleplaces');
exports.placesDetails = function (req, res) {
var places = new GooglePlaces('MY_KEY');
var params = {
placeid: req.params.placeId,
};
//this method call will be replaced by the test stub
places.details(params, function (err, response) {
var updatedResponse = 'updated body here'
res.send(updatedResponse)
});
};
test.js
var should = require('should'),
//seem weird but include it. The new version we're making will get injected into the app
GooglePlaces = require('node-googleplaces');
request = require('supertest'),
path = require('path'),
sinon = require('sinon'),
describe(function () {
before(function (done) {
//create your stub here before the "app" gets instantiated. This will ensure that our stubbed version of the library will get used in the controller rather than the "live" version
var createStub = sinon.stub(GooglePlaces, 'details');
//this will call our places.details callback with the 2nd parameter filled in with 'hello world'.
createStub.yields(null, 'hello world');
app = express.init(mongoose);
agent = request.agent(app);
done();
});
it('should get the data', function (done) {
agent.get('/api/gapi/places/search/elmersbbq')
.end(function (err, res) {
if (err) {
return done(err);
}
console.log(res.body)
done();
});
});
})

The only way I'm thinking about to do it is to change your method to:
exports.placesDetails = function (req, res, places)
create additional method:
exports.placesDetailsForGoogle = function (req, res) {
exports.placesDetails(req, res, new GooglePlaces('MY_KEY'));
}
and write a test that executes placesDetails, passing properly mocked 'places' object. You'll test placesDetails logic with this and at the same time you'll have comfy function to be used in actual code without need to actualy instantiate GooglePlaces object every time.

Related

Testing with Sinon-Chai that an individual callback in a Node JS Express route with multiple callbacks is called

I'm relatively new to Node and Sinon. This application was made with Express, and I'm using Mocha, Chai, and Sinon. Using Sinon-Chai, I'm POST testing routes in Express with multiple callbacks, and can't figure out how to check second and subsequent callbacks.
The route inside my index.js is:
var controller = require('./example.controller');
var validator = require('./example.validator');
var router = express.Router();
router.post('/', validator.create, controller.create);
In my validator.js is the validator.create which checks the submitted parameter:
exports.create = function(req, res, next) {
var valid = true;
var errorMessages = [];
if (req.body.name) {
patt = /[^a-zA-Z0-9 !##$%^&*()_+\-=\[\]{};':]/g;
if (patt.test(req.body.name)) {
valid = false;
errorMessages.push("Parameter is not alphanumeric");
}
}
if (valid == false) {
return res.status(400).json(errorMessages);
}
next();
}
In my controller.js is the controller.create which creates a entry in the DB:
exports.create = function(req, res) {
return Example.create(req.body)
.then(baseController.respondWithResult(res, 201))
.catch(baseController.handleError(res));
}
The Sinon-Chai tests in my index.spec.js:
var proxyquire = require('proxyquire').noPreserveCache();
var exampleCtrlStub = {
create: 'exampleCtrl.create',
};
var exampleValidatorStub = {
create: 'exampleValidator.create'
}
var routerStub = {
get: sinon.spy(),
put: sinon.spy(),
patch: sinon.spy(),
post: sinon.spy(),
delete: sinon.spy()
};
var exampleIndex = proxyquire('./index.js', {
express: {
Router() {
return routerStub;
}
},
'./example.controller': exampleCtrlStub,
'./example.validator': exampleValidatorStub
});
describe('POST /api/examples', function() {
it('should route to example.validator.create', function() {
routerStub.post
.withArgs('/', 'exampleValidator.create')
.should.have.been.calledOnce;
});
});
describe('POST /api/examples', function() {
it('should route to example.controller.create', function() {
routerStub.post
.withArgs('/', 'exampleCtrl.create')
.should.have.been.called;
});
});
Though expecting both tests to pass, the first test (validator.create) passes but the second one (controller.create) fails. I've not been able to find a way to test that the controller.create is called.
in the second test, we can't skip the first validator argument using withArgs. The test is failed because it is looking for a method with this signature which is not exist in source.
router.post('/', controller.create);
withArgs always start with the first then second argument, etc. So, the solution is to include the validator in the test
routerStub.post
.withArgs('/', 'exampleValidator.create', 'exampleCtrl.create')
.should.have.been.called;
Reference:
https://sinonjs.org/releases/v7.2.3/mocks/#expectationwithargsarg1-arg2-
Hope it helps

Can you make Supertest wait for an Express handler to finish executing?

I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)

Node Error: Route.get() requires callback functions but got a [object Undefined]

I have not found a question with a similar setup... how do I fix this?
I'm using node, express routing, request to call a token from an api, and async series to keep everything clean. I simplified the code by only showing one function in the async series.
routes.js
var express = require('express')
var router = express.Router()
var isAuthenticated = require("./passportAuth.js")
var tokens = require('./tokens')
module.exports = function() {
router.get('/allTokens', isAuthenticated, tokens())
return router
}
./tokens.js
var request = require("request")
var async = require('async')
module.exports = function(req, res, next) {
var allTokens = function(callback) {
request('url', function(err, res, body) {
if(err) return callback(err, null)
return callback(null, 'success')
})
}
var asyncFinally = function(err, results) {
if(err) return next(err)
res.send(results)
}
async.series([allTokens], asyncFinally)
}
Error message
Route.get() requires callback functions but got a [object Undefined]
The router is expecting a function value but you are passing in an invoked function tokens(). Try just tokens.
You are prematurely calling the tokens() function rather than just passing a reference to it. Change this:
router.get('/allTokens', isAuthenticated, tokens())
to this:
router.get('/allTokens', isAuthenticated, tokens)
Remember that any time you put () after a function name that means to call it now (immediately). Any time you just pass the function name by itself, that just passes a reference to the function that can be called later at the appropriate time (that's what you want here). This is a very common mistake.
Since calling tokens() returns undefined, that is what you end up passing to router.get() and thus why you get the specific error message you see.

using nodejs, mocha, chai and sinon i would like to stub a function that is called in a route

I have a pretty simple app that is nodejs, express and mysql
I am new to unit testing and i think this app is a great way to do better.
what i am trying to accomplish ( and i think sinon is the answer) is to mock or stub mysql.insertEventIntoDB and rabbit.addToRabbitMQ
in my app i have
app.use('/sendgrid', sendgrid(pool, config, logger) );
in my sendgrid.js i have
var express = require('express');
var mysql = require('../app/utils/mysql');
var rabbit = require('../app/utils/rabbitMQ');
module.exports = function (dbpool, config, logger) {
var router = express.Router();
router.post('/callback', function (req, res) {
for (var x=0 ; x < req.body.length ; x++ ){
mysql.insertEventIntoDB(dbpool, req.body[x], logger);
rabbit.addToRabbitMQ(config,req.body[x], logger)
}
res.json({ status: 'OK' });
});
return router;
}
i have seen lots of example of stubs and spys but just can not figure out how to do it from these test. this is an example of one of my tests
it('should get an OK for delivered POST', function(done) {
chai.request(server)
.post('/sendgrid/callback')
.send(delivered)
.end(function(err, res){
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('status');
res.body.status.should.equal('OK');
done();
});
});
thanks for any help/direction
Please try to use sinon.stub
var stub = sinon.stub(object, "method");
To fake the mysql.insertEventIntoDB
var fakeInsertEvt = sinon.stub(mysql, 'insertEventIntoDB');
Then to define its behavior when this fake function is called, the parameter of onCall is the number of this function is called.
fakeInsertEvt.onCall(0).return(0);
Or according to var stub = sinon.stub(object, "method", func);, to fake the above function with one callback function
var fakeInsertEvt = sinon.stub(mysql, 'insertEventIntoDB', function(){
return Math.random();
});
In your case, it seems the second option could be better since there is for loop out of the mysql.insertEventIntoDB.

How do you Mocha test if else statements?

I am having trouble trying to write a Mocha test for an if else statement in Node.js. Here is what is being tested:
exports.restrict = function(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
This is how I am trying to test it:
describe('interface', function(){
it('should show ability to asynchronously deny a user with an incorrect password', function(done){
function restrict (req, res, next){
if (req.session.user) {
next();
done();
} else {
req.session.error.should.equals('Access denied');
req.redirect.should.equals('/login');
done();
}
}
});
});
The error I receive when running the test is that it say timeout because it exceeds 2000ms. What am I doing wrong?
In your test you define a restrict function which is never called, so done is never called and the test times out. This is what you are doing wrong. There's nothing special to using if... else in Mocha tests.
Wider issue: what you show in your question suggests that you duplicate the code of your module into your test suite. That's a terrible way to go about testing code. You should structure your module so that your test suite is able to exercise its functions without duplication of code.
Yes, there was a incorrect statement in your test file. Reading your source file, we have two scenarios to check for restrict function.
if user session exist, check if next() is called
if user session not exist, check if redirect() is called and req.session.error is assigned
Based on top bullets, we can design our test such as
const sinon = require('sinon');
const chai = require('chai');
const assert = chai.assert;
const src = require('./src');
describe('interface', function () {
it('calls next() if user session exist', function () {
const next = sinon.spy();
const res = sinon.spy();
const req = {
session: {
user: 'username' // set user session
}
}
src.restrict(req, res, next);
assert(next.called);
});
it('redirects if user session not exist', function () {
const next = sinon.spy();
const res = {
redirect: sinon.spy()
}
const req = {
session: {}
}
src.restrict(req, res, next);
assert.equal(req.session.error, 'Access denied!');
assert(res.redirect.calledWith('/login'));
assert.isFalse(next.called)
});
});
we don't need to use done() because restrict() is not async function.
We also use sinon here to spy req, res and next variable.
Ref:
- https://sinonjs.org/
Hope it helps.

Resources