Trying to test a Node.js Server process using Mocha - node.js

Fairly new to Node.js
Made an app that runs a server process and serve files (does not use express or any frameworks), Now I'm trying to unit test it.
I'm trying to use a mocha test for that... I intended to start my server process and then run requests against it to test the expected results (stats code, body content and the likes)
However it's not working properly, all the request fail to connect to the server... I'm pretty sure that the issue is because node is juts running one process loop, the server is not running "in the background" while the queries run or possibly the server is not running yet (started ASYNC) while the request are being made ?
Anyway I was wondering what was the proper way to test this, I assume that either I need to have the server run in the background (like a forked process) and/or maybe I need to find a way to wait for the server process to be "up" first but not sure how.
Or at least recommendations on testing such server process (with Mocha or other).
Thanks.
Here is example test code (Updated since original question)
var server = new Server302('./fixture/');
var instance;
describe('Tests', function() {
before(function(done) {
instance = http.createServer(function(request, response) {
console.log(request.url);
server.serve(request, response);
}).listen(8000);
instance.on("listening", function() {
console.log("started");
done();
});
});
after(function(done){
instance.close();
console.log("stopped");
done();
});
it("Should fetch test.html", function(done) {
console.log("test1");
http.get("http://localhost:8000/", function(res) {
res.on('data', function(body) {
console.log(body)
expect(body).toEqual("test");
done();
});
})
});
It seem to Execute in order but still fails with a connection error, whereas it works when testing manually with the browser:
started
test1
․․․stopped
✖ 1 of 1 tests failed:
1) Tests Should fetch test.html:
Error: connect ECONNREFUSED
at errnoException (net.js:670:11)
at Object.afterConnect [as oncomplete] (net.js:661:19)

In your before don't call done until you get the "listening" event fired by the server.
before(function(done) {
instance = http.createServer(function(request, response) {
console.log(request.url);
server.serve(request, response);
}).listen(8000);
instance.on("listening", function() {
console.log("started");
done();
});
});
That should ensure your test connections don't start before the server is ready.
See also the documentation for server.listen

Also had to deal with the body coming in chunks, here is the final thing that works, in case that helps somebody else:
var Server302 = require('../lib/server302.js'),
http = require('http'),
assert = require("assert");
var server = new Server302('./fixture/');
var instance;
describe('Tests', function() {
before(function(done) {
instance = http.createServer(function(request, response) {
server.serve(request, response);
}).listen(8100);
instance.on("listening", function() {
done();
});
});
after(function(done) {
instance.close();
done();
});
it("Should fetch test.html", function(done) {
console.log("test1");
var body = "";
http.get({host: "localhost", port:8100, path: "/"}, function(res) {
res.on('data', function(chunk) {
// Note: it might be chunked, so need to read the whole thing.
body += chunk;
});
res.on('end', function() {
assert.ok(body.toString().indexOf("<a href='/dummy.txt'>") !== -1);
assert.equal(res.statusCode, 200);
done();
});
})
});
it("Should fetch dummy.txt", function(done) {
http.get({host: "localhost", port:8100, path: "/dummy.txt"}, function(res) {
res.on('data', function(body) {
assert.equal(res.statusCode, 200);
assert.ok(body.toString().indexOf("test") === 0);
done();
});
});
});
it("Should get 404", function(done) {
http.get({host: "localhost", port:8100, path: "/qwerty"}, function(res) {
assert.equal(res.statusCode, 404);
done();
});
});
});

Using SuperTest
Here is a full and straightforward example using SuperTest and Mocha:
var server = new Server302('./fixture/');
var request = require('supertest');
describe('Tests', function() {
it('Should fetch test.html', function(done) {
request(server)
.get('/')
.expect('test', done);
});
});
SuperTest allows you to:
Request your server using SuperAgent (much easier to use than the low level http agent).
Bound your server to an ephemeral port so there is no need to keep track of ports (you can still do it manually if needed).
Use sugary expect methods that works with Mocha (or any other test framework).

Related

Test caching with Mocha and Supertest

I have an express app that implements some caching/memoization in a few endpoints (the calls can take several seconds to run, so I save results for various amounts of time depending on the nature of the endpoint). When I'm running the unit tests via Mocha using Supertest, however, each call is taking the longer period of time. How can I actually test that the caching portion of my application is functioning properly?
When I run the node server by itself, I can see the time for each return (e.g. 3979.848 ms for the first, 3.180 ms for the second), and it is working as intended, but the tests aren't behaving the same way.
What do I need to do to test the caching/memoization? Can it be done with these tools? Do I need to utilize some other module?
My code looks like this:
var supertest = require('supertest');
var base_url = 'http://localhost:3000';
var server = supertest(base_url);
describe('big section', function() {
describe('test section', function() {
it('should do a thing', function(done) {
this.timeout(10000);
server
.get('/url1')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
//stuff
done();
});
});
});
describe('test section', function() {
it('should do a similar thing, but faster', function(done) {
this.timeout(10000); //I should be able to reduce this a lot
server
.get('/url1')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
//stuff
done();
});
});
});
});

Timeout handling with node.js stream piping

I'm piping to a file an HTTPS request, it works ok 99.9% of calls, but occasionally (maybe when server or network are not available) hangs indefinitely...
This obviously cause my application to stop working and requiring a manual restart...
I have other https connections that used to occasionally hang that always complete now using the following error code on the request object, as suggested on node documentation:
request.on('socket', function(socket) {
socket.setTimeout(10000);
socket.on('timeout', function() { request.abort(); });
});
request.on('error', function(e) {
// Handle the error...
console.error("FAILED!");
});
... but it seems that timeouts on the request are ignored if the destination is piped to a file stream, maybe I should handle an error with a timeout on the filesystem object, but the documentation is not clear if there is an event I have to wait for except for 'finish'...
Here is the sample code, I hope someone can help me:
var https = require('https'),
fs = require('fs');
var opts = {
host: 'www.google.com',
path: '/',
method: 'GET',
port: 443
};
var file = fs.createWriteStream('test.html');
var request = https.request(opts, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(function(){
console.log("OK!");
});
});
});
request.on('socket', function(socket) {
socket.setTimeout(10000);
socket.on('timeout', function() { request.abort(); });
});
request.on('error', function(e) {
console.error("FAILED!");
});
request.end();
If you wanna try the hang, change host and path with a huge file and disconnect the network cable during the transfer, it should time out after 10 seconds, but it doesn't...
I set up a demo node.js http server that sends a very slow answer and a client similar to your sample code.
When I start the client and then stop the server while sending the response then I also don't get a timeout event on the socket but I get a end event on the response within the client:
var request = https.request(opts, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(function(){
console.log("OK!");
});
});
response.on('end', function() {
// this is printed when I stop the server
console.log("response ended");
});
});
```
Maybe you could listen to that event?

How do I launch server for multiple mocha chai-http test files?

I am starting my node server in my before block on my mocha chai-http tests.
I have it working perfect for single test files. However when I attempt to run multiple tests in a single command NODE_ENV=test mocha test/**/*.js I am getting an error.
I tried to have the node servers launch on different ports per test file. This didn't work, got node server start errors.
I'm now thinking it would be great if I can have a single mocha file that runs before my other test files to start the server and then a single file that runs after the other test files to kill/stop the server.
How would I go about this.
Below is some of my code:
Here is one of my test files for reference:
var chai = require('chai');
var chaiHttp = require('chai-http');
chai.use(chaiHttp);
var expect = chai.expect;
var Sails = require('sails');
describe('REST User API', function() {
var app; // for access to the http app
var sails; // for starting and stopping the sails server
before(function (done) {
Sails.lift({
port: 3001,
log: {
level: 'error'
}
}, function (_err, _sails) {
if(_err){
console.log("Error!", _err);
done();
}
else {
app = _sails.hooks.http.app;
sails = _sails;
done();
}
});
});
describe("user session", function () {
var res; // http response
var authenticatedUser;
before(function (done) {
chai.request(app)
.post('/users/signin')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send({ email: 'admin#test.com', password: 'secret'})
.end(function (_res) {
res = _res; // Record the response for the tests.
authenticatedUser = JSON.parse(_res.text); // Save the response user for authenticated tests
done();
});
});
it("should connect with a 200 status", function () {
expect(res).to.have.status(200);
});
it("should have a complete user session", function () {
var userSession = authenticatedUser;
expect(userSession).to.have.property('firstName');
expect(userSession).to.have.property('lastName');
expect(userSession).to.have.property('gender');
expect(userSession).to.have.property('locale');
expect(userSession).to.have.property('timezone');
expect(userSession).to.have.property('picture');
expect(userSession).to.have.property('phone');
expect(userSession).to.have.property('email');
expect(userSession).to.have.property('username');
expect(userSession).to.have.property('confirmed');
expect(userSession).to.have.property('status');
expect(userSession).to.have.property('authToken');
});
});
after(function (done) {
sails.lower(function() {
done()
});
});
});
From mocha v8.2.0, you can use GLOBAL FIXTURES to setup and teardown your web server for all test suites. Global fixtures are guaranteed to execute once and only once.

done method "ignored" in beforeEach in mochajs test

I have a unit test for my wrapper around a web socket client. Here is the code to the test:
describe('server', function(){
var server;
beforeEach(function(done) {
server = new Server(function() {
//try to connect to the server on the expected port
var socket = new WebSocket('ws://localhost:8081');
});
server.wss.on('connection', function(client) {
server.wss.close();
done();
});
});
describe('#server', function(){
it('starts a server on a given port', function(done) {
var test = 1;
test.should.be.ok;
});
});
});
the issue that i'm running into is that while done is called properly (if i call done a second time right after the first time, i get an error that it was called twice) it does not seem to have any effect. Namely the test will fail after two second with:
Error: timeout of 2000ms exceeded
I'm kind of new at this, so i probably missed something easy...
Thanks, olivier
As usual, once you post the question you find the answer.
The trick is to call done inside each of the tests too.
describe('server', function(){
var server;
beforeEach(function(done) {
server = new Server(function() {
//try to connect to the server on the expected port
var socket = new WebSocket('ws://localhost:8081');
});
server.wss.on('connection', function(client) {
server.wss.close();
done();
});
});
describe('#server', function(){
it('starts a server on a given port', function(done) {
var test = 1;
test.should.be.ok;
=====> done();
});
});
});

Catching ECONNREFUSED in node.js with http.request?

I'm trying to catch ECONNREFUSED errors when using a HTTP client in node.js. I'm making requests like this:
var http = require('http');
var options = { host: 'localhost', port: '3301', path: '/', method: 'GET' };
http.request(options).on('response', function (res) {
// do some stuff
});
I can't figure out how to catch this error:
Error: connect ECONNREFUSED
at errnoException (net.js:614:11)
at Object.afterConnect [as oncomplete] (net.js:605:18)
If I do request.on('error', function () {});, it doesn't catch it. If I do it like this:
var req = request.on(etc)
req.on('error', function blah () {});
Then I get TypeError: Object false has no method 'on'.
Do I really have to do a top-level uncaught error thing to deal with this? At the moment whatever I do my whole process quits out.
Edit: I found some blog posts on how to do it by creating a connection object, calling request on that, and then binding to errors on the connection object, but doesn't that make the entire http.request() shortcut useless?
Any reason you're not using http://nodejs.org/docs/v0.6.5/api/http.html#http.request as your base? Try this:
var req = http.request(options, function(res) {
// Bind 'data', 'end' events here
});
req.on('error', function(error) {
// Error handling here
});
req.end();
Each call to http.request() returns its self.
So try it like this...
http.request(options.function(){}).on('error',function(){}).end();
I've got a solution for this, having tried all the suggestions on this (and many other) pages.
My client needs to detect a turnkey product that runs embedded windows. The client is served from a different machine to the turnkey.
The turnkey can be in 3 states:
turned off
booted into windows, but not running the turnkey app
running the turnkey app
My client sends a 'find the turnkey product' GET message to my nodejs/express service, which then tries to find the turnkey product via http.request. The behavior for each of the 3 use cases are;
timeout
ECONNREFUSED - because the windows embedded phase of the turnkey is
refusing connections.
normal response to request (happy day scenario)
The code below handles all 3 scenarios. The trick to catching the ECONNREFUSED event was learning that its handler binds to the socket event.
var http = require('http');
var express = require('express');
var url = require('url');
function find (req, res) {
var queryObj = url.parse(req.url, true).query;
var options = {
host: queryObj.ip, // client attaches ip address of turnkey to url.
port: 1234,
path: '/some/path',
}; // http get options
var badNews = function (e) {
console.log (e.name + ' error: ', e.message);
res.send({'ok': false, 'msg': e.message});
}; // sends failure messages to log and client
// instantiate http request object and fire it
var msg = http.request(options, function (response) {
var body = '';
response.on ('data', function(d) {
body += d;
}); // accumulate response chunks
response.on ('end', function () {
res.send({'ok': true, 'msg': body});
console.log('sent ok');
}); // done receiving, send reply to client
response.on('error', function (e) {
badNews(e);
}); // uh oh, send bad news to client
});
msg.on('socket', function(socket) {
socket.setTimeout(2000, function () { // set short timeout so discovery fails fast
var e = new Error ('Timeout connecting to ' + queryObj.ip));
e.name = 'Timeout';
badNews(e);
msg.abort(); // kill socket
});
socket.on('error', function (err) { // this catches ECONNREFUSED events
badNews(err);
msg.abort(); // kill socket
});
}); // handle connection events and errors
msg.on('error', function (e) { // happens when we abort
console.log(e);
});
msg.end();
}
For those not using DNS (you can also use request instead of get by simply replacing get with request like so: http.request({ ... })):
http.get({
host: '127.0.0.1',
port: 443,
path: '/books?author=spongebob',
auth: 'user:p#ssword#'
}, resp => {
let data;
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => console.log(data));
}).on('error', err => console.log(err));

Resources