Building tests with mocha for async code (request) - javascript

I am trying to create unit tests with Mocha and Chai on Node.JS. Here is a simplified version of the function to test:
router.cheerioParse = function(url, debugMode, db, theme, outCollection, _callback2) {
var nberror = 0;
var localCount = 0;
console.log("\nstarting parsing now : " + theme);
request(url, function(error, response, body) {
//a lot of postprocessing here that returns
//true when everything goes well)
});
}
Here is the test I am trying to write:
describe('test', function(){
it('should find documents', function(){
assert( true ==webscraping.cheerioParse("http://mytest.com, null, null, null ,null,null ));
});
})
How can the request function return true to have it passed to the test? I have tried to use promises but it didn't work either. In this case should I put the return statement in the then callback? What is the best approach?

You should mock request function. You could use e.g. sinon stubs for this (they provide returns function for defining returning value).
In general - the idea of unit tests is to separate particular function (unit of test) and stub every other dependency, as you should do with request :)
To do so, you have to overwrite original request object, e.g. :
before(function() {
var stub = sinon.stub(someObjectThatHasRequestMethod, 'request').returns(true);
});
And after running tests you should unstub this object for future tests like that:
after(function() {
stub.restore();
});
And that's all :) You could use both afterEach/after or beforeEach/before - choose the one that suits you better.
One more note - because your code is asynchronous, it is possible that your solution might need more sophisticated way of testing. You could provide whole request mock function and call done() callback when returning value like this:
it('should find documents', function(done) {
var requestStub = sinon.stub(someObjectThatHasRequestMethod, 'request',
function(url, function (error, response, body) {
done();
return true;
}
assert(true === webscraping.cheerioParse("http://mytest.com, null, null, null ,null,null ));
requestStub.restore();
});
You could find more info here:
Mocha - asynchronous code testing

Related

How to unit test a function which calls another that returns a promise?

I have a node.js app using express 4 and this is my controller:
var service = require('./category.service');
module.exports = {
findAll: (request, response) => {
service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
It calls my service which returns a promise. Everything works but I am having trouble when trying to unit test it.
Basically, I would like to make sure that based on what my service returns, I flush the response with the right status code and body.
So with mocha and sinon it looks something like:
it('Should call service to find all the categories', (done) => {
// Arrange
var expectedCategories = ['foo', 'bar'];
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.resolves(expectedCategories);
var response = {
status: () => { return response; },
send: () => {}
};
sandbox.spy(response, 'status');
sandbox.spy(response, 'send');
// Act
controller.findAll({}, response);
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
expect(response.send).to.be.called; // not working
done();
});
I have tested my similar scenarios when the function I am testing returns itself a promise since I can hook my assertions in the then.
I also have tried to wrap controller.findAll with a Promise and resolve it from the response.send but it didn't work neither.
You should move your assert section into the res.send method to make sure all async tasks are done before the assertions:
var response = {
status: () => { return response; },
send: () => {
try {
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
// expect(response.send).to.be.called; // not needed anymore
done();
} catch (err) {
done(err);
}
},
};
The idea here is to have the promise which service.findAll() returns accessible inside the test's code without calling the service. As far as I can see sinon-as-promised which you probably use does not allow to do so. So I just used a native Promise (hope your node version is not too old for it).
const aPromise = Promise.resolve(expectedCategories);
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.returns(aPromise);
// response = { .... }
controller.findAll({}, response);
aPromise.then(() => {
expect(response.status).to.be.calledWith(200);
expect(response.send).to.be.called;
});
When code is difficult to test it can indicate that there could be different design possibilities to explore, which promote easy testing. What jumps out is that service is enclosed in your module, and the dependency is not exposed at all. I feel like the goal shouldn't be to find a way to test your code AS IS but to find an optimal design.
IMO The goal is to find a way to expose service so that your test can provide a stubbed implementation, so that the logic of findAll can be tested in isolation, synchronously.
One way to do this is to use a library like mockery or rewire. Both are fairly easy to use, (in my experience mockery starts to degrade and become very difficult to maintain as your test suite and number of modules grow) They would allow you to patch the var service = require('./category.service'); by providing your own service object with its own findAll defined.
Another way is to rearchitect your code to expose the service to the caller, in some way. This would allow your caller (the unit test) to provide its own service stub.
One easy way to do this would be to export a function contstructor instead of an object.
module.exports = (userService) => {
// default to the required service
this.service = userService || service;
this.findAll = (request, response) => {
this.service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
var ServiceConstructor = require('yourmodule');
var service = new ServiceConstructor();
Now the test can create a stub for service and provide it to the ServiceConstructor to exercise the findAll method. Removing the need for an asynchronous test altogether.

testing promised service with Chai and Sinon

I'm stuck with testing promies in Chai and Sinon. Generally I got service with is wrapper for xhr request and it returns promises. I tried to test it like that:
beforeEach(function() {
server = sinon.fakeServer.create();
});
afterEach(function() {
server.restore();
});
describe('task name', function() {
it('should respond with promise error callback', function(done) {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
service.get('/someBadUrl').then(spy1, spy2);
server.respond();
done();
expect(spy2.calledOnce).to.be.true;
expect(sp2.args[0][1].response.to.equal({status: 404, text: 'Not Found'});
});
});
My notes about this:
// spy2 is called after expect finish assertion
// tried with var timer = sinon.useFakeTimers() and timer.tick(510); with no results
// tried with chai-as-promised - don’t know how use it :-(
// cannot install sinon-as-promised only selected npm modules available in my environment
Any any ideas how fix this code/ test this service module?
There's various challenges here:
if service.get() is asynchronous, you need to wait for its completion before checking your assertions;
since the (proposed) solution checks the assertions in a promise handler, you have to be careful with exceptions. Instead of using done(), I would opt for using Mocha's (which I assume you're using) built-in promise support.
Try this:
it('should respond with promise error callback', function() {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
// Insert the spies as resolve/reject handlers for the `.get()` call,
// and add another .then() to wait for full completion.
var result = service.get('/someBadUrl').then(spy1, spy2).then(function() {
expect(spy2.calledOnce).to.be.true;
expect(spy2.args[0][1].response.to.equal({status: 404, text: 'Not Found'}));
});
// Make the server respond.
server.respond();
// Return the result promise.
return result;
});

Stubbing a promisified function with sinon and bluebird

In the file I would like to test, I have the following code:
var httpGet = Promise.promisify(require("request").get);
httpGet(endpoint, {
auth: {bearer: req.body.access_token},
json: true
})
.then(...)
Now, in my tests, I want to make sure that httpGet was called once, and make sure the parameters are valid. Before being promisified, my test looked like this:
beforeEach(function () {
request.get = sinon.stub()
.yields(null, null, {error: "test error", error_description: "fake google error."});
});
afterEach(function () {
expect(request.get).to.have.been.calledOnce();
var requestArgs = request.get.args[0];
var uri = requestArgs[0];
expect(uri).to.equal(endpoint);
//...
});
Unfortunately this no longer works when request.get is promisified. I tried stubbing request.getAsync instead (since bluebird appends "Async" to promisified functions), but that does not work either. Any ideas?
Promise.promisify doesn't modify the object, it simply takes a function and returns a new function, it is completely unaware that the function even belongs to "request".
"Async" suffixed methods are added to the object when using promisify All
Promise.promisifyAll(require("request"));
request.getAsync = sinon.stub()
.yields(null, null, {error: "test error", error_description: "fake google error."});
expect(request.getAsync).to.have.been.calledOnce();
Just for future reference I've solved this a bit differently, and I think a little cleaner. This is typescript, but basically the same thing.
fileBeingTested.ts
import * as Bluebird from 'bluebird';
import * as needsPromise from 'needs-promise';
const methodAsync = Bluebird.promisify(needsPromise.method);
export function whatever() {
methodAsync().then(...).catch(...);
}
test.spec.ts
import * as needsPromise from 'needs-promise';
import * as sinon form 'sinon';
const methodStub = sinon.stub(needsPromise, method);
import { whatever } from './fileBeingTested';
Then you use the methodStub to manage what calls happen. You can ignore that it's being promisified and just manage it's normal behavior. for example if you need it to error.
methodStub.callsFake((arg, callback) => {
callback({ error: 'Error' }, []);
});
The promisified version will throw the error and you'll get it in the catch.
Anyone coming across this. I have small utility func
function stubCBForPromisify(stub) {
let cbFn = function() {
let args = [...arguments];
args.shift();
return stub(...args);
};
return cbFn.bind(cbFn, () => ({}));
}
In test
var getStub = sinon.stub().yields(null, {error: "test error", error_description: "fake google error."})
sinon.stub(require("request"), 'get', stubCBForPromisify(getStub))
expect(getStub).to.have.been.calledOnce();
I was running into trouble testing this using tape and proxyquire. I'm not sure what pattern/framework people are using that allowed them to modify the required'd request object directly as shown in the accepted answer. In my case, in the file I want to test I require('jsonFile'), then call bluebird.promisifyAll(jsonFile). Under normal conditions this creates a readFileAsync method that I want to stub. However, if during testing I try to use proxyquire to pass in a stub, the call to promisifyAll overwrites my stub.
I was able to fix this by also stubbing promisifyAll to be a no-op. As shown this might be too coarse if you rely on some of the async methods to be created as-is.
core.js:
var jsonFile = require('jsonfile');
var Promise = require('bluebird');
Promise.promisifyAll(jsonFile);
exports.getFile = function(path) {
// I want to stub this method during tests. It is
// created by promisifyAll
return jsonFile.readFileAsync(path);
}
core-test.js:
var proxyquire = require('proxyquire');
var tape = require('tape');
var sinon = require('sinon');
require('sinon-as-promised');
tape('stub readFileAsync', function(t) {
var core = proxyquire('./core', {
'jsonfile': {
readFileAsync: sinon.stub().resolves({})
},
'bluebird': { promisifyAll: function() {} }
});
// Now core.getFile() will use my stubbed function, and it
// won't be overwritten by promisifyAll.
});

Writing express.js app. Where do helper methods go?

So I have started with express.js - my first JS web dev framework. I didn't make anything small, but started a bigger project. I'm learning, and building at the same time.
Coming from a Python/Flask background, express seems very complicated.
Like in python, if I want a helper method, I can just put it on top of the file, or in a new module, and import it. Super easy. But in node/express, things are async, and everything is in exports or module.exports (??). Where do helper methods go? How do I call them with callbacks?
In another question I asked, I was doing the same kind of computation multiple times. In Python, I would write a method (with if statements and parameters), and call it multiple times, using a for.. in... loop. The code I have right now is very redundant.
How do I do it in express? What are best practices for writing express code?
It really depends of what your helper is doing. If it operates with data which is passed as a parameter to it then you may save it in an external module and use require to access it.
// helpers/FormatString.js
module.exports = function(str) {
return str.toUpperCase();
}
// app.js
var formatter = require("./helpers/FormatString");
However, if you need to modify the request or the response object then I'll suggest to define it as a middleware. I.e.:
app.use(function(req, res, next) {
// ... do your stuff here
});
#Krasimir gave a correct answer. Regarding your question how to deal with asynchronous helper functions I can give you an example (not the usual foo/bar, but one of my own helper functions):
var cached; // as modules can act like singletons I can share values here
module.exports = function fetchGoogleCerts(options, callback) { // <-- callback
var now = Date.now();
if (ttl > now && cached) {
callback(null, cached); // <-- return with success
return;
}
request({
uri: options.certsUrl || defaultCertsUrl,
strictSSL: true
}, function (err, response, body) {
var error, certs;
if (!err && response.statusCode === 200) {
certs = jsonParse(body); // a local function
if (!certs) {
callback('parse_error', null); // <-- return an error
return;
}
cached = certs;
// ... more code
callback(null, cached); // <-- success case
} else {
error = {
error: err,
statusCode: response.statusCode
};
log.error(error, 'error fetching google certs');
callback(error); // <-- return an error
}
});
}
};
And using the helper:
fetchGoogleCerts = require('./google-certs.js'),
module.exports = function fetchCertsAndDecodeIdToken(token, options, callback) {
fetchGoogleCerts(options, function (err, googleCerts) {
if (err) {
callback({
errorCode: codes.io_error,
errorMsg: 'Unable to fetch Google certificates',
error: err
});
return;
}
decodeAndVerifyGoogleIdToken(googleCerts, token, options, callback);
});
};
As you can see above, the simple solution is to provide a callback function to your asynchronous helper function.
Of course you can also export an Object which extends EventEmitter, then you might not need a callback function, but register for the events. Here is an Example for a helper which emits events.

How to test a method in Jasmine if the code in `beforeEach` is asynchronous?

I'm trying to write some tests with Jasmine, but now have a problem if there are some code is asynchronous in beforeEach.
The sample code looks like:
describe("Jasmine", function() {
var data ;
beforeEach(function(){
console.log('Before each');
getSomeDataFromRemote(function(res){
data = res;
});
});
it("test1", function() {
expect(data).toBe(something);
console.log('Test finished');
});
});
You can see, in the beforeEach, I want to get some data from remote, and assign it to the data asynchronously.
But in the test1, when I try to verify:
expect(data).toBe(something);
The data is undefined, because getSomeDataFromRemote has not finished yet.
How to fix it?
Just like the async stuff within an it you can use the runs and waitsFor in your beforeEach:
define( 'Jasmine' , function () {
var data ;
beforeEach(function(){
runs( function () {
getSomeDataFromRemote(function(res){
data = res;
});
});
waitsFor(function () { return !!data; } , 'Timed out', 1000);
});
it("test1", function() {
runs( function () {
expect(data).toBe(something);
});
});
});
Although I'm going to assume that it's because this was test code I think you should probably have the getSomeDataFromRemote call inside your it as that's actually what you're testing ;)
You can see some larger examples in some tests I've written for an async API here: https://github.com/aaronpowell/db.js/blob/f8a1c331a20e14e286e3f21ff8cea8c2e3e57be6/tests/public/specs/open-db.js
Jasmine 2.0
Be careful because in the new Jasmine 2.0 this is going to change and it will be mocha style. You have to use done() function in beforeEach() and it(). For example, imagine you want to test if a page exists and is not empty, in a LAMP server, using jQuery $.get. First you need to add jQuery to the SpecRunner.html file, and in your spec.js file:
describe('The "index.php" should', function() {
var pageStatus;
var contents;
beforeEach(function (done) {
$.get('views/index.php', function (data, status) {
contents = data;
pageStatus = status;
done();
}).fail(function (object, status) {
pageStatus = status;
done();
});
});
it('exist', function(done) {
expect(status).toBe('success');
done();
});
it('have content', function(done) {
expect(contents).not.toBe('');
expect(contents).not.toBe(undefined);
done();
});
});
As you can see, you pass the function done() as a parameter for beforeEach() and it(). When you run the test, it() won't be launched until done() has been called in beforeEach() function, so you are not going to launch the expectations until you have the response from the server.
The page exists
If the page exists we capture the status and the data from the response of the server, and we call done(). Then we check if the status is "success" and if the data is not empty or undefined.
The page does not exist
If the page does not exist we capture the status from the response of the server, and we call done(). Then we check if the status is not "success" and if the data is empty or undefined (that must be because the file does not exist).
In this case I typically stub the asynchronous call to respond immediately.
I'm not sure if you've seen it or not, but here is some documentation about asynchronous testing with Jasmine.

Categories