Unit testing an endpoint controller with Node and Rewire - node.js

I am using rewire to test my node controllers. I have the following endpoint that uses request to GET some data.
exports.myGetEndpoint = function(req, res) {
return request.get({
url: 'http://baseURL/api/myGetEndpoint',
headers: {
authorization: { //etc }
},
json: true
})
.then(function(data) {
res.status(200).json(data.objects);
});
};
I want to test that when I call the get method from the controller, that request gets called with the correct arguments, but I'm lost on how to either 'stub' or 'spy' on request.
var Promise = require('bluebird');
var rewire = require('rewire');
var controller = rewire('../../controllers/myGetEndpoint.js');
var request = {
get: sinon.stub()
};
// Use rewire to mock dependencies
controller.__set__({
request: request
});
describe('myGetEndpoint', function() {
var json;
var req;
var res;
beforeEach(function(done) {
json = sinon.spy();
req = { headers: {} };
res = {
status: sinon.stub().returns({
json: json
})
};
controller.myGetEndpoint(req, res).then(function() {
done();
});
});
it('should call the correct request arguments', function() {
// lost
});
});

I am going to modify a bit your source code to use just a callback instead of a promise. I need to investigate how to stub a promise with sinon but you can get an idea on how it can be tested with a simple callback:
exports.myGetEndpoint = function(req, res) {
return request.get({
url: 'http://baseURL/api/myGetEndpoint',
headers: {
authorization: { //etc }
},
json: true
}, function (error, response, body) {
res.status(200).json(response.objects);
});
};
You should not call the controller in the beforeEach but you must call it inside the test case. The beforeEach normally is used to do the initializations before the test case:
var expect = require('chai').expect;
var Promise = require('bluebird');
var rewire = require('rewire');
var controller = rewire('../../controllers/myGetEndpoint.js');
var request = {
get: sinon.stub()
};
// Use rewire to mock dependencies
controller.__set__({
request: request
});
describe('myGetEndpoint', function() {
var status;
var json;
var req;
var res;
beforeEach(function(done) {
json = sinon.spy();
status = sinon.stub();
req = { headers: {} };
res = {
status: status.returns({
json: json
})
};
});
it('should call the correct response arguments', function(done) {
var response = {objects: ['objects', 'objects']}
request.get.yields(null, response);
controller.myGetEndpoint(req, res);
expect(status.calledWith(200)).to.equal(true);
expect(json.calledWith(response.objects)).to.equal(true);
});
});
P.S.: I didn't actually try to run it, so maybe there are some typos.

Related

NodeJS using request inside a loop

I use NodeJS and request lib to make some request to an API.
I understand now that all requests are async and so it doesn't "wait" the result of the GET call, so the index of my Loop is always the same.
I was wondering if there was any simple way (without any lib) to wait for the response of the request call ?
For now, my code is this :
for (var i in entries) {
var entryId = entries[i]['id'];
var options = {
url: 'https://api.com/'+ entryId +'/get/status',
method: 'GET',
headers: {
'Authorization': auth
}
};
console.log(' ENTRY ID > '+ entryId);
request(options, function(error, response, body) {
var response = JSON.parse(body);
if (response.status.code == 200) {
var id = response.status.id;
var data = [];
data['id'] = id;
data = JSON.stringify(data);
// show first entryId of loop
console.log(' > MY ID : '+ id + ' - '+ entryId);
options = {
host: hostname,
port: 80,
path: '/path/function2',
method: 'PUT'
};
var post = http.request(options, function(json) {
var body = '';
json.on('data', function(d) {
body += d;
});
json.on('end', function() {
console.log('> DONE');
});
}).on('error', function(e) {
console.log(e);
});
post.write(data);
post.end();
}
});
}
You are looking for async/await.
Wrap your logic inside an async function, then you can await for the promise to resolve.
const request = require('request-promise')
async function foo (a) {
for (i in a)
try {
let a = await request('localhost:8080/')
// a contains your response data.
} catch (e) {
console.error(e)
}
}
foo([/*data*/])
Just use the promisified version of request module.
You also can use Promises to wait for your async code to finish.
function asyncCode(msg, cb){
setTimeout(function() {cb(msg);}, 1000);
}
var p1 = new Promises(function(resolve){
asyncCode("my asyncCode is running", resolve);
});
p1.then(function(msg) {
console.log(msg);
}).then(function() {
console.log("Hey I'm next");
});
console.log("SyncCode, Async code are waiting until I'm finished");

Why is my return empty (Node.js, unirest)

I have a problem with my little Node.js test setup. I basically want an endpoint that I can call, and this endpoint can call different other endpoints and give me a JSON as a response. When I have a look at the console output of the performRequest function, everything looks good. But the return of this function doesn't get passed. I always get an empty {} as a response.
The routes.js that holds my routes:
var s24 = require("./s24");
var routes = function(app) {
app.get("/ping", function(req, res) {
res.send("<p>pong</p>");
console.log("Received GET");
});
app.get("/getCategories", function(req, res) {
var output = s24.getCategories();
res.type('application/json');
return res.send(output);
});
};
module.exports = routes;
The s24.js that queries another REST-API:
var functions = require('./functions');
var appID = "XYZ";
var authorization = "ABC";
var getCategories = function () {
var output = functions.performRequest("https://api.s24.com/v3/"+appID+"/categories", authorization);
console.log(output);
return output;
};
module.exports.getCategories = getCategories;
The functions.js that holds all my relevant functions:
var unirest = require('unirest');
var performRequest = function(endpoint,authorization,body) {
unirest.get(endpoint)
.headers({'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': authorization})
.send(body)
.end(function (response) {
var data = response.body;
console.log(data);
return data;
});
};
module.exports.performRequest = performRequest;
performRequest is an asynchronous function usually in node.js you can't just return the response of an asynchronous function. You can use async await feature with babel but the simplest way is just to use callbacks or promises.
Your code should look something as following:
var performRequest = function (endpoint, authorization, body, callback) {
unirest.get(endpoint)
.headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': authorization
})
.send(body)
.end(function (err, response) {
var data = response.body;
callback(data["id"]);
});
}
var getCategories = function () {
var output = functions.performRequest(
"https://api.s24.com/v3/" + appID + "/categories",
authorization,
function (output) {
console.log(output);
});
};
As you can see passing the response from one callback to an other is seems ugly so I suggest you to rethink your design.

sinon spy as callback not being called

I have my source file for which i have written test cases for
var debug = require('debug')('kc-feed:source:fb');
var request = require('request');
var config = require('../../config').root;
exports.source = function fetchFeed (callback) {
var params = {
headers: {'content-type' : 'application/jsons'},
url: config.host + "v1/social/fb/sync_feed",
method: 'GET'
};
request(params, function(err, body, response) {
if(err) {
callback(err);
}
else {
var raw_data = JSON.parse(response);
callback(null, raw_data);
}
});
};
This is my mocha test case
var chai = require('chai'),
expect = chai.expect,
app = require('../app'),
rewire = require('rewire'),
fbfeed = rewire('../src/feed/source/fb_feed'),
supertest = require('supertest'),
sinon = require('sinon'),
nock = require('nock');
describe('test cases for fb feed file', function() {
var callback;
beforeEach(function(){
callback = sinon.spy();
});
after(function(){
nock.cleanAll();
});
it('callback should be called with the response', function(done) {
nock('http://localhost:3000/')
.get('/v1/social/fb/sync_feed')
.reply(200, {});
callback = sinon.spy();
fbfeed.source(callback);
sinon.assert.calledOnce(callback);
done();
});
it('callback should be called with the error', function(done) {
nock('http://localhost:3000/')
.get('/v1/social/fb/sync_feed')
.replyWithError(new Error('err'));
fbfeed.source(callback);
sinon.assert.calledOnce(callback);
done();
});
Both my test cases fail as it says that callback is called 0 times. But the callback is always called. Please help.
It looks like the requests are still being performed asynchronously (I can't definitively say if this is expected when using nock), so you can't use spies like that.
Provide a regular callback instead:
fbfeed.source(function(err, raw_data) {
...make your assertions...
done();
});

Jasmine doesn't create spy on a function

I've got following test: (simplified)
var request = require('request');
var routes = require('../routes.js');
describe('routes', function() {
var req, res;
beforeEach(function(){
req = { headers: {}, url: 'http://127.0.0.1', params: {} };
res = {
sendFile: function() { return '' },
render: function() { return '' },
json: function() { return {} }
};
jasmine.createSpy(request, 'get')
});
it('should render static page', function() {
routes.show(req, res);
expect(request.get).toHaveBeenCalled();
})
});
and routes.js contents:
module.exports = {
show: function(req, res) {
request.get(apiUrl('v1/users/' + req.params.name), apiErrorsHandler(function(body) {
res.render('users/show.jade', body);
}, function() {
res.json({});
}));
}
}
I'm using gulp-jasmine if it matters, but the problems happens anyway even when I use jasmine-node.
Each time I run a test I receive following error:
Error: Expected a spy, but got Function.
on line:
expect(request.get).toHaveBeenCalled();
Do you have any idea why would that happen?
I've tried following solution: Expected a spy, but got Function but without success since .get is attached directly to a request so the reference was undefined.
jasmine.createSpy does not work quite like that..
What you want is to create a spy and assign it to the get property of the request object.
req = { headers: {}, url: 'http://127.0.0.1', params: {}, get: jasmine.createSpy('reqGetSpy')};

How to send json, after async function complete

I'm using expressjs.
I have a router:
exports.index = function(req, res){
if(req.param('name')) {
var simpleParser = require('../tools/simpleParser');
var result = simpleParser.images(req.param('name'));
// how i can get result from simpleParser.images after it complete?
res.json(result);
}
res.render('goods');
};
An i have a simpleParser.images:
module.exports = {
images: function (url) {
if (url) {
var request = require('request'),
cheerio = require('cheerio');
request({
uri: url,
method: 'GET',
encoding: 'binary'
}, function (err, res, body) {
var tmp = [];
body = new Buffer(body, 'binary');
var $ = cheerio.load(body);
$('.products-listing li a').each(function () {
var link = $(this).find('img').attr('src');
tmp.push(link);
});
// How i can send tmp to router, when it complete?
});
}
}
};
When i asking page with ?name it return null, because request in simpleParser.images work async. How i can subscribe to result of simpleParser request function, and send json after it complete?
Like many node modules, you can provide a callback in your own utility functions. Your simpleParser.images function is not synchronous, as it uses the request module. You can have your simpleParser.images function accept a callback that will be called upon the completion of the network request and some data parsing.
var request = require('request'),
cheerio = require('cheerio');
module.exports = {
images: function (url, callback) {
if (!url) callback(null, null);
request({
uri: url,
method: 'GET',
encoding: 'binary'
}, function (err, res, body) {
if (err) callback(err);
var tmp = [];
body = new Buffer(body, 'binary');
var $ = cheerio.load(body);
$('.products-listing li a').each(function () {
var link = $(this).find('img').attr('src');
tmp.push(link);
});
// Here we have the data and can pass it in the callback
callback(null, tmp);
});
}
};
Then you essentially have your own function that can be performed asynchronously. Then in your
express route, that is async as well, so just plug in your new function
if (req.param('name'))
simpleParser.images(req.param('name'), function (err, images);
res.json(images);
});
} else {
res.render('goods');
}

Resources