Quantcast
Channel: BeyondFog » node.js
Viewing all articles
Browse latest Browse all 3

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

$
0
0

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 StriderCD.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

Strider will work with any test framework, assertion libraries, etc. The only requirement is that Strider needs to be able to start a test run by executing ‘npm test’. To do this, you need to add a line to package.json with your desired test framework and any necessary arguments. Strider works best with results in TAP so we have added that as an argument:

"scripts": { "test": "mocha -R tap" }

After I added the Zombie tests, when I ran ‘npm test’, Mocha failed on the first Zombie test with the following error:

Error: global leaks detected: o, section, data, m, k, i

To resolve this, I added those globals to the test command, which now looks like this:

"test": "mocha -R tap --globals o,section,data,m,k,i"

Another way to resolve this would be to add –ignore-leaks at the end of the mocha command, however, by instead allowing only these particular globals, Mocha will fail again if any new global variables appear in the code.

Test Driven Development (TDD) and the importance of done()

When I first wrote these Zombie tests, they were all succeeding. I eventually determined that one of them should be failing, and couldn’t figure out why until Niall reminded me that I needed to specify done as the callback and then invoke done() at the completion of each test. Had I been doing proper TDD I would have discovered this right at the start!

Mongod configuration – no preallocate

After I started dropping the test database after each run, the test run would start failing pretty regularly due to one or more of the tests timing out. The reason for this is that MongoDB pre-allocates space when first creating a new database and that pre-allocation can take a while. To avoid pre-allocation, start your dev MongoDB instance with the following arguments:

mongod --noprealloc --nojournal

Running Poang on Heroku/MongoLab

Procfile

Heroku requires a Procfile that specifies how to start the web application. Poang’s Procfile is one line:

web: node app.js

Environment Variables for Database Connectivity

Heroku provides UNIX environment variables to connect to MongoLab. Strider also provides UNIX environment variables for database connectivity. In order to work with both, Poang first checks to see if the Heroku-provided environment variable is present, if it isn’t there, it then looks for the Strider-provided environment variable, and if neither is present, it uses the local dev database as specified in the config file. Here’s the relevant code:

var db_uri=process.env.MONGOLAB_URI || process.env.MONGODB_URI || config.default_db_uri;

Add MongoLab module to Heroku via Heroku Toolbelt

After you create your application on Heroku, you need to add the MongoLab add-on to the application before it will run. The mongolab:starter plan is free.

A) If you are using Strider to test and deploy Poang, you will need to run the following command from your command line (using the Heroku Toolbelt) after Strider creates the Heroku app:

heroku addons:add mongolab:starter --app [your_app_name]

B) If you created the heroku app yourself using the Heroku Toolbelt and you are in the local directory for that app, you may not need to specify the app name:

heroku addons:add mongolab:starter

That’s it, your app should now be up and running on Heroku and MongoLab!


Stephen Bronstein is a co-founder of BeyondFog, the creators of StriderCD.com, a hosted continuous integration and deployment platform for Python and node.js.


Viewing all articles
Browse latest Browse all 3

Latest Images

Trending Articles





Latest Images