Testing Strategies for Continuous Deployment of a Node.JS & MongoDB app to Heroku/MongoLab

Poang (github) is a very basic Node.js/MongoDB app that will run on Heroku & MongoLab. This app demonstrates a few of the ways that we write tests in Node.js for Strider. (Strider is our hosted continuous integration and deployment platform for Node.js and Python. Learn more at StriderApp.com)

Poang was built using the Express framework. It uses Everyauth for local authentication, Mongoose-Auth to connect Everyauth to MongoDB (and Mongoose as the ODM) for account persistence, and Connect-Mongo as a session store. Much of the code in app.js was generated by Express and all of the code in auth.js after the Comment schema definition is straight from the Mongoose-Auth docs.

For testing, Poang uses the Mocha test framework, should for assertions, Sinon.JS for mocks & stubs, and Zombie.js for lightweight integration testing.

Unit Testing

It can be challenging to create simple unit tests with a basic web/database application because so much of the code relates to database reads and writes. Poang didn’t have any functions that were simple enough, so I added a function for this purpose – timesTwo(). As you might expect, timesTwo() takes a number as an input and returns that number…times two.

Here is the unit test for timesTwo():

describe(‘index’, function() {
  describe(‘#timesTwo()’, function() {
    it(‘should multiply by two’, function() {
      var x = 5;
      var xTimesTwo = index.timesTwo(x);
      xTimesTwo.should.equal(10);
    });
  });
});
The first three lines of the test are Mocha setup – first we specify the file, then the function, then the behavior that we expect. (I will exclude these lines in subsequent code snippets)

The main body of the test is very simple – it executes timesTwo() with an input of 5, and then uses should.js to verify that the output is 10.

Using a Sinon spy to verify middleware functionality

Next, I wanted to verify that my middleware function requires user login to do anything in the app. I created ‘mock_req’ and ‘mock_res’ objects that I could pass to the middleware. I used Sinon’s spy function to watch the redirect function within the mock response object, and then called the middleware with mock_req, mock_res, and the ‘next’ function (which in this case just sets the http status to 200).

var mock_req = {session: {}};
var mock_res = {redirect: function() {}, end: function() {}};
sinon.spy(mock_res,"redirect");
middleware.require_auth_browser(mock_req, mock_res, function() {
  mock_res.statusCode = 200;
});
The middleware determines if the user is logged in by checking to see if there is a user object in the request object. Since ‘mock_req’ didn’t include a user object, the middleware should return a status code of 401 (Authorization Required) and should redirect to ‘/login’:

mock_res.statusCode.should.eql(401);
mock_res.redirect.getCall(0).args[0].should.equal(‘/login’);
I then created a new ‘mock_req’ which includes an empty user object and again call the middleware function. This time it should call the ‘next’ function:

mock_req = {user: {}};
middleware.require_auth_browser(mock_req, mock_res, function() {
  mock_res.statusCode = 200;
});
mock_res.statusCode.should.eql(200);
Zombie for lightweight browser integration testing

Next I wanted to add a few lightweight integration tests using Zombie.js. Since Zombie is a headless browser, for these tests we need to startup an instance of Poang in the ‘before’ block, using a random number between 4096 and 65535 for the server port. (Ideally the code would also check to make sure that port is open before listening on it)

var TEST_PORT = Math.floor(Math.random()*61439 + 4096);
before(function() {
   var server = app.init(config);
   // should check to see if something is listening on the port first
   server.listen(TEST_PORT);
   console.log(‘Server is listening on port %s’, TEST_PORT);
});
The first Zombie test just checks to make sure the front page loads. As you can see, it’s very little code:

var browser = new zombie();
browser.visit(base_url, function () {
  browser.success.should.be.ok;
  if (browser.error) {
    console.dir(‘errors reported:’, browser.errors);
  }
done();
});
The next test checks the title of the front page. The only difference here is that the ‘browser.success’ line was replaced with the following:

browser.text("title").should.eql("Poang");
The last test goes through the registration process and verifies that Poang then redirects to ‘/’:

var browser = new zombie();
browser.visit(base_url + "register", function () {
  browser.query("#register").should.be.ok;
  // Fill email, password and submit form
  browser.
    fill("email", test_email).
    fill("password", "secret").
    pressButton("register", function() {
      // Form submitted, new page loaded.
      browser.success.should.be.ok;
      browser.location.pathname.should.eql("/");
      done();
    });   
});
Because the last test attempts to register with the same information every time, we need to delete at least this record from the database or else the test will fail the next time around. The cleanest thing to do is to drop the entire test db. This is done in the after() function:

after(function(done) {
  var db_uri = process.env.MONGOLAB_URI || process.env.MONGODB_URI || config.default_db_uri;
  mongoose.connect(db_uri);
  // drop database
  mongoose.connection.db.executeDbCommand( {dropDatabase:1}, function() {
    console.log("Dropped test database");
    done();    
  });
})
npm test

Read More:

http://blog.beyondfog.com/testing-cd-node-mongo-app-heroku/?utm_source=rss&utm_medium=rss&utm_campaign=testing-cd-node-mongo-app-heroku#.T7RqTugzCE4

Did you like this? Share it: