how to write unit test to "request" node module with sinon.stub? - javascript

I have this function in my code :
let request = require("request");
let getDrillDownData = function (userId, query, callback) {
query.id = userId;
let urlQuery = buildUrlFromQuery(query);
request.get({
url: urlQuery,
json: true
}, function (error, response, data) {
if (!error && response.statusCode === 200) {
return callback(null, calculateExtraData(data));
} else if (error) {
return callback(error, null);
}
});
};
and I wish to write some unit test which verify that when the function is called with correct parameters, it is running OK,
and if there is an error, it did return the error
I wrote this unit test code :
describe.only('Server Service Unit Test', function(){
var sinon = require('sinon'),
rewire = require('rewire');
var reportService;
var reportData = require('./reportData.json');
beforeEach(function(){
reportService = rewire('../../services/reports.server.service');
});
describe('report methods', function(){
var reportData;
var query = { id: "test"};
var userId = 'testuser';
var getDrillDownData;
var request;
beforeEach(function(){
getDrillDownData = reportService.__get__('getDrillDownData');
});
it ('should get drill down data by userId and query', function(done){
var getCallback = sinon.stub();
request = {
get: sinon.stub().withArgs({
url: query,
json: true
}, getCallback.withArgs("error", {statusCode: 200}, reportData))
};
reportService.__set__('request', request);
getDrillDownData(userId, query, function(err, returnData){
(err === null).should.eql(true);
//(getCallback.withArgs(undefined, {statusCode: 200}, reportData).calledOnce).equal(true);
done();
});
});
});
But I keep getting this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
Can someone help?
Thanks

I would stub request.get() directly:
describe('report methods', function() {
// Make `request.get()` a Sinon stub.
beforeEach(function() {
sinon.stub(request, 'get');
});
// Restore the original function.
afterEach(function() {
request.get.restore();
});
it ('should get drill down data by userId and query', function(done) {
// See text.
request.get.yields(null, { statusCode : 200 }, { foo : 'bar' });
// Call your function.
getDrillDownData('userId', {}, function(err, data) {
...your test cases here...
done();
});
});
});
Using request.get.yields() (which calls the first function argument that Sinon can find in the argument list; in this case, it's the (error, response, data) callback that gets passed to request.get() in your function) you can tell Sinon which arguments to use to call the callback.
That way, you can check if the callback to request.get() handles all arguments properly.
You can use .withArgs() too (request.get.withArgs(...).yields(...)), although you have to be sure that you're using it correctly; otherwise, if the exact arguments don't match, Sinon will call the original request.get() instead of using the stubbed version.
Instead, I prefer using stub.calledWith() to check for the correct arguments after the call has been made. That integrates much better with Mocha as well, as you can use explicit assertions.

Related

Nodejs callback with parameter

My friend and I have been struggling with Node.js callbacks since yesterday. We have the following function:
// helperFunction.js
function foo(param) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
return array;
});
}
module.exports.foobar = foo;
then we call that from our app.js.
Since yesterday, we have updated the code to wait for the callback by using a function, like so:
// app.js
//var bar = require('./helperFunction');
//console.log(helperFunction.foobar('param')); // prints undefined
function bar(){
console.log('Log something')
}
foo(bar);
but we don't know how to pass the parameter to foo. I tried to add param (which is a string) to bar but it doesn't work.
For the record, I'm aware of other posts such as this, but I cannot make it work on my code.
In foo you just add a callback parameter and instead of returning you call this function. As a convention, the first parameter of the callback function is the error object. If no error occurred, this object is null and the following parameters are the actual result of the function. Your code didn't include error handling so I added it. If error exists you won't receive any data and foo can't calculate whatever it tries to calculate. In this case, foo should either try to solve the problem itself or propagate the error to its caller.
function foo(param, cb) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
if (err) {
return cb(err);
}
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
cb(null, array);
});
}
function bar(err, data){
console.log('Log something')
}
foo('some param', bar);
Pass a function to foo. Something like:
foo(() => bar("Hi, I'm something"));
function foo(fn, err) {
if (!err && fn instanceof Function) {
fn();
}
}
function bar(someThing){
console.log(`Log ${someThing}`);
}

Callback spy does not getting called on stream end

I can't get my head around why this is not working. I have a module which sends a HTTP POST request containing some payload using the native nodejs http module. I am stubbing the request method with sinon and pass the PassThrough stream for the request and response streams.
DummyModule.js
const http = require('http');
module.exports = {
getStuff: function(arg1, arg2, cb) {
let request = http.request({}, function(response) {
let data = '';
response.on('data', function(chunk) {
data += chunk;
});
response.on('end', function() {
// spy should be called here
cb(null, "success");
});
});
request.on('error', function(err) {
cb(err);
});
// payload
request.write(JSON.stringify({some: "data"}));
request.end();
}
};
test_get_stuff.js
const sinon = require('sinon');
const http = require('http');
const PassThrough = require('stream').PassThrough;
describe('DummyModule', function() {
let someModule,
stub;
beforeEach(function() {
someModule = require('./DummyModule');
});
describe('success', function() {
beforeEach(function() {
stub = sinon.stub(http, 'request');
});
afterEach(function() {
http.request.restore()
});
it('should return success as string', function() {
let request = new PassThrough(),
response = new PassThrough(),
callback = sinon.spy();
response.write('success');
response.end();
stub.callsArgWith(1, response).returns(request);
someModule.getStuff('arg1', 'arg2', callback);
sinon.assert.calledWith(callback, null, 'success');
});
});
});
The spy does not get called and the test fails with AssertError: expected spy to be called with arguments. So the response.on('end', ...) does not get called and therefore the test failure. Does the end event on the response stream needs to be triggered somehow?
It's working now. First, the events need to be emitted using the emit method.
Second, the events need to be emitted right after the someModule.getStuff(...) method call:
...
someModule.getStuff('arg1', 'arg2', callback);
response.emit('data', 'success');
response.emit('end');
sinon.assert.calledWith(callback, null, 'success');
...

Mocha: how to test this async method that returns a callback?

I'd like to run a unit-test for a method like below.
returnsCallback (callback) {
return callback(null, false)
}
The callback arguments being passed in are, I believe, (error, response). I can't figure out how to test this or if it's even appropriate to test. Do I mock the callback argument to test it? I have below so far, and it seems to work, but I'm not sure. Any ideas?
describe('returnsCallback ()', function () {
let myObj = new MyObject()
it ('should always return false', function (done) {
myObj.returnsCallback(function (error, response) {
if (!error && response === false) {
done(false)
} else {
done(true)
}
})
})
})
You should test not that callback returns false, but that it's called with correct parameters. In your case they are null and false. I don't know what assertion library do you use, I show an example with using should module:
describe('returnsCallback ()', () => {
let myObj = new MyObject();
it ('should call callback with error is null and response is false', done => {
myObj.returnsCallback((error, response) => {
should(error).be.exactly(null);
should(response).be.equal(true);
done();
});
});
});

Don't know how to test this asyn function with Jasmine

asynFn(url, callback)
This function takes a url and fires some xhr requests, then uses callback(result) to send back processed result. How should I test it?
(I've run the asynFn directly in Chrome and it worked fine.)
I tried to use jasmine-ajax to stub the request, but the expect did't work.
describe('a test', function() {
var callback
beforeAll(function() {
jasmine.Ajax.install()
jasmine.Ajax.stubRequest('fake/path1').andReturn({
status: 200,
contentType: 'text/plain',
responseText: 'yay'
})
jasmine.Ajax.stubRequest('fake/path2').andReturn({
status: 200,
contentType: 'text/plain',
responseText: 'yay2'
})
// ...
})
afterAll(function() {
jasmine.Ajax.uninstall()
})
beforeEach(function() {
callback = jasmine.createSpy('sendResponse')
})
it('a spec', function() {
asynFn('input string', callback)
expect(jasmine.Ajax.requests.mostRecent().url).toBe('fake/path2')
expect(callback).toHaveBeenCalled() // faild
})
})
What am I missing here?
The problem is that asynFn is asynchronous and the callback y called after the expect sentence is executed.
Think you test like a history.
Subject Under Testing (describe)
When asynFn is executed (beforeEach)
Then: A method or callback should be called (it)
Change your code to:
beforeEach(function() {
callback = jasmine.createSpy('sendResponse');
asynFn('input string', callback);
});
afterEach(function() {
callback = null;
});
it('a spec', function() {
expect(jasmine.Ajax.requests.mostRecent().url).toBe('fake/path2')
expect(callback).toHaveBeenCalled() // faild
})
if The first doesn't work, try this:
beforeEach(function(done) {
callback = jasmine.createSpy('sendResponse');
asynFn('input string', function() {
callback();
done(); //<-- This tells jasmine tha async beforeEach is finished
});
});

MeteorJS async code inside synchronous Meteor.methods function

How to get the client method.call to wait for an asynchronous function to finish? Currently it reaches the end of the function and returns undefined.
Client.js
Meteor.call( 'openSession', sid, function( err, res ) {
// Return undefined undefined
console.log( err, res );
});
Server.js
Meteor.methods({
openSession: function( session_id ) {
util.post('OpenSession', {session: session_id, reset: false }, function( err, res ){
// return value here with callback?
session_key = res;
});
}
});
Recent versions of Meteor have provided the undocumented Meteor._wrapAsync function which turns a function with a standard (err, res) callback into a synchronous function, meaning that the current Fiber yields until the callback returns, and then uses Meteor.bindEnvironment to ensure that you retain the current Meteor environment variables (such as Meteor.userId()).
A simple use would be as the following:
asyncFunc = function(arg1, arg2, callback) {
// callback has the form function (err, res) {}
};
Meteor.methods({
"callFunc": function() {
syncFunc = Meteor._wrapAsync(asyncFunc);
res = syncFunc("foo", "bar"); // Errors will be thrown
}
});
You may also need to use function#bind to make sure that asyncFunc is called with the right context before wrapping it.
For more information see: https://www.eventedmind.com/tracks/feed-archive/meteor-meteor-wrapasync
I was able to find the answer in this gist. In order to run asynchronous code from within a method.call you use Futures which forces your function to wait.
var fut = new Future();
asyncfunc( data, function( err, res ){
fut.ret( res );
});
return fut.wait();
Update: Sorry, I should have read the question more carefully. It looks like this question was also asked and answered here.
Apart from futures, another pattern to consider might be updating another model with the data returned from the asynchronous call and then subscribing to that model's changes.
From the meteor.call documentation it looks like the the result argument (err, res) of your callback function should contain the output of your openSession function. But you aren't returning any values from your openSession function so the return value is undefined.
You can test this:
Client:
Meteor.call('foo', function(err, res) {
console.log(res); // undefined
});
Meteor.call('bar', function(err, res) {
console.log(res); // 'bar'
});
Server:
Meteor.methods({
foo: function() {
var foo = 'foo';
},
bar: function() {
var bar = 'bar';
return bar;
}
});

Categories