A canonical web test in NodeJS
Working with web applications in NodeJS is great. Using the same language and libraries on the client and server simplified the thinking. And NodeJS has fast tests and restart for a super quick edit-verify cycle when you’re coding.
I like to write tests to verify the server-side and client-side logic, but do you know that the whole solution really is working? You can of course test your service manually after deploying, but that becomes tedious. By using Selenium, we can test that the solution works end-to-end.
In this article, I show how to write a Mocha test that:
- Starts up the Selenium test runner
- Creates a browser client that can access the application (using PhantomJS)
- Starts up the server and sends the browser to the server (running in ExpressJS)
- Clicks a button in the web page that triggers some JavaScript (written in jQuery in this example)
- Verifies that the call to the server returns and displays correct data
This test can be run with a simple command after you check out the code and requires no setup on the developers computer!
describe("web application", function() {
before(function(done) {
this.timeout(5000);
client = startWebDriver(function() {
startServer(function(url) {
client.init().url(url, done);
});
});
});
after(function(done) {
client.end(done);
})
it("clicks menu item", function(done) {
client.click("#showMore", function(err) {
client.waitFor("#results div", 1000, function(err) {
client.getText("#results div", function(err, text) {
expect(text).to.have.string("Here's more");
done();
});
});
});
});
});
This test starts a new server and client for each test. When using this for real, you really want to make sure you only start the server and the web driver once as these are expensive operations.
The test case of “it clicks menu item” has a lot of callbacks. There are versions of webdriver APIs for NodeJS which are based on promises that you may enjoy using more.
Implementation details
The full application can be found on Github. To implement it, I used the following NPM modules:
- phantomjs
- selenium-standalone
- webdriverjs
- expressjs (of course)
- mocha and chai (of course)
Starting the server looks like this:
var startServer = function(done) {
var app = require('../app');
var server = app.listen(0, function() {
var port = server.address().port;
done("http://localhost:" + port + "/", done);
});
}
And the app.js file looks like this:
var express = require('express');
var app = express();
// set up app routes here
module.exports = app;
I have a server.js file which starts up the express application to run normal (outside the tests).
A simplified version of starting Selenium and WebDriver looks like this:
var startWebDriver = function(done) {
var selenium = require('selenium-standalone');
var server = selenium({ stdio: 'pipe' });
// call done when server is started
var client = require('webdriverjs').remote({
desiredCapabilities: {browserName: 'phantomjs'}
});
return client;
}
I found that Selenium and PhantomJS has some issues, at least on Windows, so my final code needed a few hacks to work. See the full details at github.
Conclusion
A web end to end integration test in NodeJS requires a little bit of shaking your fist at the heavens to get to work the first time, not in the least due to the need to work around limitations in Selenium and PhantomJS. But once you got it up and running, you can easily test not only that your logic works, but that your whole application works together.
When making these tests, I allowed for a little flexibility as well: By setting environment variables, the same tests can be run with a manually deployed server, so you can use it to verify that your staging server is up and running (for example). And of course, you can replace PhantomJS as a web browser with Firefox or Chrome and see the tests run for real in your browser.
Automate the end-to-end tests of your NodeJS applications!