.   .   .
We're always looking for great talent‼️ 🚀😄 And we're passionate about driving innovation and improving lives. Join us as we help social impact organizations and enterprise customers build their web apps. Apply today!
.   .   .

Over the past few months, we’ve been getting really into Node here at Chiedo Labs. And man, it’s powerful. With that being said, you know how the saying goes. With great power comes great responsibility. If you’re not writing tests for your code, it’s quite arguable that you aren’t being responsible. So we stopped being lazy and started looking into options. I’m not here to debate. We ended up going with Mocha and will be explaining our setup.

Our setup

We built our app using Express, Mongoose and babel for es6 features. As long as you’re doing that also, this should be for you. It’s also worth noting that we use Bluebird to override the default Mongoose Promises.

Some core libraries

You’ll need a few libraries before anything.

  • Mocha – The testing framework
  • Chai – Gives you some useful tools for testing, such as expect/should functions.
  • Mockgoose – Allows you to run Mongoose in-memory instead of connecting to a persistent database.
  • Bluebird – A promise library
  • Factory-girl – For creating factories for your models.
  • Faker – For creating randomized data
  • Supertest – For performing requests in your testsnpm install –save-dev mocha chai mockgoose bluebird factory-girl faker supertest

Now that you’ve got the needed packages. We’re ready to start talking about all of the individual steps.

Let’s start with the factory

First we need to handle all of our imports. Let’s place our file in ./test/factory.js

import factoryGirl from 'factory-girl';
import User from '../app/models/User';
import Post from '../app/models/Post';
import faker from 'faker';
import bluebird from 'bluebird';
...

 

Next, we want to promisify factory-girl which will make testing much easier later on.

...
let factory = factoryGirl.promisify(bluebird);
...

 

Now we’re ready to actually define our factories. You’ll see that we use faker to generate default data. You’ll notice we use the es6 arrow syntax for all of the fields except for the url_path. The reason is that we needed the this behavior of standard functions in that last case. I won’t dive into the details but it’s worth looking into if you are confused

...
factory.define('user', User, {
  name: () => faker.name.findName(),
  email: () => faker.internet.email(),
  username: () => faker.internet.userName(),
  password: () => faker.internet.password(),
});
    
factory.define('post', Post, {
  title: () => faker.lorem.sentence(),
  body: () => faker.lorem.paragraphs(),
  url_path: function() {
    return this.title.replace(/\s/g, '-') + Date.now();
  },
});
    
export default factory;
...

Your complete file should end up like this:

//./test/factory.js
    
import factoryGirl from 'factory-girl';
import User from '../app/models/User';
import Post from '../app/models/Post';
import faker from 'faker';
import bluebird from 'bluebird';
    
let factory = factoryGirl.promisify(bluebird);
    
factory.define('user', User, {
  name: () => faker.name.findName(),
  email: () => faker.internet.email(),
  username: () => faker.internet.userName(),
  password: () => faker.internet.password(),
});
    
factory.define('post', Post, {
  title: () => faker.lorem.sentence(),
  body: () => faker.lorem.paragraphs(),
  url_path: function() {
    return this.title.replace(/\s/g, '-') + Date.now();
  },
});
    
export default factory;

Now we’ll create a basic test helper

This is pretty basic, so I won’t go into too many details here. Let’s name in ./test/test-helper.js The one thing to note though is how we use mockgoose. Once we call mockgoose with mongoose as a parameter, mongoose will automagically run in-memory from then on.

// ./test/test-helper.js
    
import mongoose from 'mongoose';
import mockgoose from 'mockgoose';
    
mockgoose(mongoose);
    
/*
 * Creates and/or connects to a mongo test database in memory
 * @param {function} cb callback function
 * @returns {void}
 */
module.exports.createDB = (cb) => {
  mongoose.connect('mongodb://localhost/test', cb);
};
    
/*
 * Disconnects from and destroys the mongo test database in memory
 * @returns {void}
 */
module.exports.destroyDB = () => {
  mongoose.disconnect();
};

Getting your entry point ready

Whether your entry point is named app.js, index.js, server.js or whatever, this is what I’m referring to. In our case, it’s named app.js. The key here is that you need to update your app.js to bind to a random port if in test mode. You’ll also want a conditional that doesn’t start your database if the environment is set to ‘test’.

let env  = process.env.NODE_ENV;
    
let express       = require('express');
let app           = express();
    
// If the environment is a test environment, then leaving this as undefined will make it random, which we want for test mode.
let port;
if(env !== 'test') {
  port = process.env.PORT || 8000;
}
    
let server = app.listen(port, () => {
  let host = server.address().address;
  let port = server.address().port;
    
  console.log(`Example app listening at http://${host}:${port}`);
});
    
module.exports = server;

Now add a script to your package.json

You will want a script to run your tests. Make sure you have babel 6 installed. This script makes sure babel is the compiler for the JS, the test directory is tested and all the tests within it are executed.

"scripts" {
  ...
  "test": "NODE_ENV=test mocha test --recursive --compilers js:babel/register --reporter spec"
  ...
 }

Now let’s write a test

We’re going to write a test that makes sure a user can register and makes sure once that user is registered, another user can’t create an account with the same email address. Let’s create a file named ./test/controllers/users-controller-test.js. First, we’ll start with our imports. Don’t worry about the getToken function too much. For now, just know that it returns a JWT authentication token for a user which is needed to authenticate a request.

import { expect } from 'chai';
import request from 'supertest';
import factory from '../factory.js';
import app from '../../app';
import { createDB, destroyDB } from '../test-helper';
import { getToken } from '../../app/utils/functions';
...

 

Next we need to prepare for some setup and tear down. We make sure that before the test a database is created and after the test, it’s destroyed.

...
describe('Users', () => {
  before((done) => {
    createDB(() => {
      done();
    });
  });
    
  after(() => {
    destroyDB();
  });
...

 

Lastly, we create our tests. You can see more complicated tests in the example repo but these are pretty straightforward and should all read like english.

describe('Create', () => {
  it('should register a user when given the correct credentials', (done) => {
    let user = factory.buildSync('user');
      
    request(app)
    .post('/users')
    .send({user: user})
    .expect(200)
    .then((res) => {
      expect(res.body.user).to.be.an('object');
      done();
    });
  });
});

 

Concluding thoughts

Writing tests for your codebases can be frustrating but it’s worth overcoming. I can name countless times when I’ve caught bugs with my tests that I had no idea were there. This shouLd be more than enough to get you started but if you’re still having trouble and need help building initial tests for your codebases, feel free to reach out to my team at labs@chiedo.com.

.   .   .

We're Hiring‼️ ?? Looking to join our team of web developers? We're passionate about innovation, family, and community. Apply today!