Race condition when running functional tests in Sails.js using Mocha and Grunt

I am using Sails v0.10.x and have an issue when running my functional tests.

test/bootstrap.test.js

// force the test environment to 'test'
process.env.NODE_ENV = 'test';

var Sails = require('sails');

// use zombie.js as headless browser
var Browser = require('zombie');

// Global before hook
before(function(done) {
  var self = this;
  // Lift Sails and start the server
  Sails.lift({
    log: {
        level: 'error'
    },
  }, function(err, sails) {
    // initialize the browser using the same port as the test application
    self.browser = new Browser({ site: 'http://localhost:1337', debug: false });

    done(err, sails);
  });
});

// Global after hook
after(function(done) {
  Sails.lower(done);
  this.browser.close();
});

The problem is Sails.lift triggers the default Grunt task to run. One of the things this task does is clean out the public folder and then copy assets across.

The issue I have is my functional tests are running whilst this copying is still happening. This means I get lots of 404 errors (and failing tests) when my headless browser is requesting static assets.

I'm guessing there could be several solutions

  • add a news "sails lift" command that doesn't clean down the public folder (this could cause issues when running tests after deployments though?)
  • adding a timeout after sails lifts before firing up my headless browser (this seems hacky though)
  • some kind of callback/event (not sure this is possible?)

What solutions have others come up with for this problem?

You can try lifting the app in production mode:

Sails.lift({
  log: {
      level: 'error'
  },
  environment: 'production'
}, function(err, sails) {...}

In production mode, Sails doesn't finish lifting until all Grunt tasks are complete.

If this isn't an option for you (because you require your tests to run in a different environment with specific settings), you can instead listen for the hook:grunt:done event and not start your test until you receive it. You'll need to turn off the "watch" task for the environment you're running the tests in (otherwise Grunt will never be done!). That means if you want to keep "watch" for your development environment, you'll need to run the tests in a different environment (which is a good idea anyway). So first, in tasks/register/default.js:

module.exports = function (grunt) {
  var tasks = ['compileAssets', 'linkAssets'];
  if (process.env.NODE_ENV !== 'test') {tasks.push('watch');}
    grunt.registerTask('default', tasks);
};

Then in your test:

var async = require('async');
async.parallel({
  // Set up a listener for grunt finish
  listenForGrunt: function(cb) {sails.on('hook:grunt:done', cb);},
  // Lift sails
  liftSails: function(cb) {
    Sails.lift({
      log: {
        level: 'error'
      },
    }, function(err, sails) {
      self.browser = new Browser({ site: 'http://localhost:1337', debug: false });
      cb(err, sails);
    });
  }
}, done);

Run your tests with NODE_ENV=test mocha, or better yet in your package.json under scripts, put:

"test": "NODE_ENV=test mocha"

So that you can just run npm test.

This will ensure that done is not called until Sails has lifted and Grunt is finished.