I’m trying to test this wrapper to request that I made:
// lib/http-client.js
var app = require('app'),
request = require('request');
exports.search = function(searchTerm, next) {
var options = {
url: 'https://api.datamarket.azure.com/Bing/Search/Web',
qs: {
'Query': ''' + searchTerm + ''',
'Adult': ''Off'',
'$format': 'json',
'$top': 10
},
auth: {
user: app.get('bing identifier'),
pass: app.get('bing identifier')
}
};
request(options, function(err, res, body){
if (err) return next(err);
// TODO: parse JSON and return results
});
};
where app
is an instance of express. The question is, how do I test the function of this “module” without having to touch the internet? If I was to do this in other languages I would have mocked the request module but I don’t know if that’s the best way to do it in node.js.
// NODE_PATH=lib
describe('Http Client', function(){
it('should return error if transport failed', function(){
var c = require('http-client'),
results = 'foo';
// request mock should return results when called
c.search('foo', function(err, results){
results.should.eql(res);
});
// TODO
});
it('should return an error if JSON parsing failed', function(){
// TODO
});
it('should return results', function(){
// TODO
});
});
If your module has a constructor that takes the app and request as constructor arguments (in other words dependency injection), you can just provided a stubbed/mocked implementation of request in your test.
If not: I know you can override in tests what RequireJS returns when requiring something, so I would assume the same would be possible in node.js. So if you can change the configuration of the module loader in a test, you can configure it to return a stubbed/mocked implementation of request.
Personally, I’d go the dependency injection route. Having to configure your module loader in tests is a pain in the backside…
I recently did this. Forgot where I read about it but it’s pretty easy.
Here’s the basics of my code.
var express = require('express');
var http = require('http');
var MyServer = function(options) {
var app = express();
app.get(/^/foo.html/, sendFoo);
app.get(/^/bar.html/, sendBar);
var server = options.server || http.createServer(app);
server.listen(options.port);
function sendFoo(req, res) {
res.writeHead(200);
res.write("foo");
res.end();
}
function sendBar(req, res) {
res.writeHead(200);
res.write("bar");
res.end();
}
this.close = function() {
server.close();
}
};
As you can see my server either users http.createServer or the one I pass in. Maybe I should always pass it in? But in any case. I made a mock server
var events = require('events');
var MockHTTPServer = function() {
var eventEmitter = new events.EventEmitter();
var self = this;
this.once = eventEmitter.once.bind(eventEmitter);
this.listen = function() {
eventEmitter.emit('listening', self, 0);
};
this.close = function() {
};
};
And a MockResponse
var MockResponse = function(callback) {
this.headers = {};
this.statusCode = -1;
this.body = undefined;
this.setHeader = function(key, value) {
this.headers[key] = value;
}.bind(this);
this.writeHead = function(statusCode, headers) {
this.statusCode = statusCode;
Object.keys(headers).forEach(function(key) {
this.headers[key] = headers[key];
}.bind(this));
}.bind(this);
this.write = function(data, encoding) {
this.body = data.toString(encoding);
}.bind(this);
this.end = function() {
var fn = callback;
callback = undefined;
fn(this);
}.bind(this);
};
Note this is not a complete response mock. I just tried it in my tests and only added the functions that were called.
To actually test I made a test server
var Promise = require('promise');
var TestServer = function(callback) {
var server = new MyServer({
server: new MockHTTPServer,
}, callback);
this.close = function() {
server.close();
};
var request = function(req, callback) {
var res = new MockResponse(callback);
server.handleRequest(req, res);
};
var getP = function(url) {
return new Promise(function(resolve, reject) {
request({ url: url, method: 'GET'}, resolve);
});
};
this.getP = getP;
this.request = request;
};
Which, using mocha, I can test like this
describe('test server', function() {
var server;
before(function(done) {
server = new TestServer(done);
});
describe('test', function() {
it('serves bar', function(done) {
server.getP("http://localhost/bar.html").then(function(res) {
res.body.should.containEql("bar");
}).then(done, done);
});
it('serves foo', function(done) {
server.getP("http://localhost/foo.html").then(function(res) {
res.body.should.containEql("foo");
}).then(done, done);
});
});
after(function(done) {
server.close();
done();
});
});
You can see I just make up request objects on the fly