Ensuring Express App is running before each Mocha Test
Asked Answered
S

6

20

I am working on developing a REST API using ExpressJS, NodeJS, Mongoose and Mocha.

The thing is that I have an app.coffee file, thats responsible for setting up ExpressJS and connecting to Mongoose. The way I have set this up is that Mongoose is connected first and if that gets through, then, the ExpressJS App is started.

The issue is that when setting up Mocha, I need to make sure that ExpressJS App existing in app.coffee is completely started successfully including all asynchronous code before any testcase is executed.

For that, I have created a test_helper.coffee and placed the following code in it, but, the testcases start their execution even if the code in app.coffee hasn't completed its execution completely which actually makes sense:

before (done) ->
  require(__dirname + '/../src/app')
  done()

In a nutshell, I want to make sure that the ExpressJS app has fully completed its setup before any testcase is executed.

How I can do that?

Septima answered 22/9, 2013 at 8:14 Comment(2)
I had faced exactly similar issue. I have redesigned my express server and it's now do not wait for database connection or any sort of asynchronous initialization. Which is also ideal for REST servers as it's not necessary to connect to database even if there is no request coming onto a REST environment. Also each call does not necessarily need all the models. In my case, I initialize database when it's necessary which almost every call does.Manpower
@Kamrul: Can you please share some sample code as how you did that?Septima
E
15

I am late to the party, but I found the best way to set up my mocha test suite for an express app is to make my app.js or server.js file export the app object, like this:

var mongoose = require('mongoose');
var express = require('express');
require('express-mongoose');

var env = process.env.NODE_ENV || 'development';
var config = require('./config/config')[env];

var models = require('./app/models');
var middleware = require('./app/middleware');
var routes = require('./app/routes');

var app = express();

app.set('port', process.env.PORT || config.port || 3000);
app.set('views', __dirname + '/app/views');
app.set('view engine', 'jade');

// database
mongoose.connect(config.db);

// middleware
middleware(app);

// Application routes
routes(app);

app.listen(app.get('port'));
console.log('Express server listening on port ' + app.get('port'));

// export app so we can test it
exports = module.exports = app;

make sure your config file has different environments like development, test, production set up:

var path = require('path');
var rootPath = path.normalize(__dirname + '/..');

module.exports = {
  development: {
    db: 'mongodb://localhost/my_dev_db',
    port: 3000
  },
  test: {
    db: 'mongodb://localhost/my_test_db',
    port: 8888
  },
  production: {
    // ...
  }
}

then in your test files you can go ahead and require your app, which will connect to the right db and on the right port:

var should = require('chai').should();
var request = require('supertest');
var mongoose = require('mongoose');

var app = require('../app');
var agent = request.agent(app);

var User = mongoose.model('User');

    // get users
    describe('GET /api/users', function() {
      it('returns users as JSON', function(done) {
        agent
        .get('/api/users')
        .expect(200)
        .expect('Content-Type', /json/)
        .end(function(err, res) {
          if (err) return done(err);
          res.body.should.have.property('users').and.be.instanceof(Array);
          done();
        });
      });
    });

And finally, to start up the whole monster you include this in your package.json (make sure to have nodemon and mocha in your devDependencies):

"scripts": {
    "start": "NODE_ENV=development ./node_modules/.bin/nodemon app.js",
    "test": "NODE_ENV=test ./node_modules/.bin/mocha --reporter spec test/**.js"
  }

Now you can start your test suite with npm test and your app with npm start.

Hope it helps! ps: most of the stuff I learned from looking at this amazing example: https://github.com/madhums/node-express-mongoose-demo

Exigible answered 15/10, 2013 at 8:55 Comment(8)
Does supertest know when the expressjs app is ready to use? How do you guarantee that the database is ready to use when your http verbs are initialized?Rescission
because I export the app from my app.js file and require it at the top of each test fileExigible
But what about the db? AFAIK you have to use something like mongoose.connection.once('open', callback) and then add your routes. This means that app is truly initialized asynchronously. How do you handle that?Rescission
it's in the code above, i use mongoose.connect(config.db)in my app.js file.Exigible
does mongoose.connect block until the database is ready? If yes, what are the events ('open' etc.) for?Rescission
might block at this point yes, but tbh here I don't care as I need the connection to proceed anyway. you could also put the routing and stuff in the callback. check mongoosejs.com/docs/connections.html for details about mongoose connectionExigible
The bare minimum to get started on testing. This will do !Featherbrain
This doesn't work as expected. Required does not wait. The app is asynchronous and could start after your tests starts. This is unlikely, but nonetheless in some cases you'll have a race condition and the tests will get run first.Nickell
B
3

I ran into the same issue while using jasmine/supertest to test my express app. I solved the issue by emitting when the app was ready and only running my tests afterwards. Here is my directory structure

- server.js
- spec
    - setup.spec.js
    - test.spec.js

In server.js when ever your server is set up and you are ready to run your tests add app.emit('started');. And make sure to export your app! In setup.spec.js you can watch for the event to be emitted.

server.js

const express = require('express');
const app = express();
module.exports = app; // for testing

setTimeout(() => {
    app.listen(process.env.PORT || 3000);
});

setup.spec.js

const server = require('../index');

beforeAll(done => server.on('started', done));

test.spec.js

const request = require('supertest');
const server = require('../index');

describe('test', () => {
    it('test server works', done => {
        request(server).get('/test').expect(200);
     });
});

The same idea should work for mocha as well. Here is an article explaining this for mocha. You should just change beforeAll to before.

Breadstuff answered 30/5, 2017 at 21:54 Comment(2)
Yep this worked for me too, just add the emit after you initilise/start the server then check app.on it in the before function - best answer by farJudkins
Is the call to setTimeout() in server.js a workaround to avoid a race condition where the 'started' event is emitted prior to 'beforeAll()' running and being ready to watch for the event?Anabantid
I
1

There might be a more straightforward way, but I went to Grunt for automating my functional tests. There's an express and mocha plugin to reach your goal. My gruntfile:

'use strict';

module.exports = function (grunt) {
grunt.initConfig({
    express: {
        options: {}
      , test: {
            options: {
                script: './app.js'
            }
        }
    }
  , simplemocha: {
        options: {
            globals: ['should']
          , timeout: 8000
          , ignoreLeaks: false
          , ui: 'bdd'
          , reporter: 'tap'
        }
      , all: { src: ['tests/*.test.js'] }
    }
})

grunt.loadNpmTasks('grunt-express-server')
grunt.loadNpmTasks('grunt-simple-mocha')

grunt.registerTask('default', ['express:test', 'simplemocha', 'express:test:stop'])
}

bonus: add 'grunt' as a git pre-commit hook. This way you cannot commit without passing all the tests

Indoor answered 23/9, 2013 at 9:43 Comment(0)
A
0

You don't need to listen a port for testing your app. You can use supertest testing library with your app and it should be ok.

You would probably need to connect to a database or redis client though. You can do this in configure method of your app. Since node caches modules, you can require app module in different test modules without reconnecting.

Aubigny answered 23/9, 2013 at 9:22 Comment(3)
The problem is that my app setup is asynchronous, including when my express routes are ready, so supertest will fail every time trying to hit that route because it is starting too soon.Goatsbeard
You do need to listen to a port. You can just allow supertest to do it for you.Klump
You don't need to listen a port for testing your app.. Oh, that's new. How does it work then?Keir
T
0

The app.listen method takes a callback that runs when everything is ready. So, you need to be able to pass the done callback there. Something like

before (done) ->
  var app = require(__dirname + '/../src/app')
  app.listen(3000, done)
Tuinal answered 5/7, 2017 at 13:47 Comment(0)
N
0

Basically you need there are two things you need to do.

  1. Make sure the database connects before the server listens
  2. Make sure the app is started before you run tests.

The framework already has an a way to handle this by using events.

Add this to your database connection, which instructs mongoose to emit 'ready' when it's finished connecting.

mongoose.connection.once('open', function() { 
   // All OK - fire (emit) a ready event. 
   console.log('Connected to MongoDB');
   app.emit('ready'); 
});

Then instruct express to wait for 'ready' before listening. When it is done it emits: 'appStarted'.

app.on('ready', function() { 
    app.listen(PORT, function(){ 
        console.log("Server listing on port:",PORT); 
        app.emit("appStarted");
    }); 
}); 

This is all good practice Mocha aside, to make sure your server starts smoothly. To finish the Mocha integration in your before (I like to use Promises):

before(function() {        
    return new Promise((resolve,reject) => {
        app.on("appStarted", function(){
            return resolve();
        }); 
    });
});
Nickell answered 25/12, 2018 at 20:42 Comment(1)
It seems like this works because the 'ready' event has enough of a delay that Mocha's before() function has already run is waiting for the 'appStarted' event. If the DB connection was super fast, you might have a race condition where 'appStarted' is fired too early and Mocha does not see it.Anabantid

© 2022 - 2024 — McMap. All rights reserved.