TDD: Testing NodeJS and React with Mocha, Chai, Sinon and Enzyme

This is going to be a continually updated post covering testing in JS development.

I’ll begin by demonstrating how to develop a NodeJS REST API using Mocha and Chai.

What is Mocha?

Mocha is a test runner which we will use to execute our tests. It doesn’t come with an assertion library, which is why you often hear of people using Mocha together with assertion libraries such as Chai.

What is Chai?

Chai is an assertion library. Whereas Mocha runs our tests, our assertion library is what we use to that things are as we expect them to be.

This is going to assume you have a reasonable understanding of JS, NodeJS and Express

Building our API with testing

First step is to install a few modules we need..

npm install express body-parser mocha chai chai-http 

We’ll start with a basic Node server

const express = require('express');
const bodyParser = require('body-parser');

let app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
	console.log(`Listening on port ${PORT}`);
})

module.exports = app;

Now that we have a functioning server we are going to begin building our API.

Our API will have the following specification

Create a folder /test in the route of the application containing the file app.test.js.

Our first test will be for the GET /contact route. We will expect it to return a status 200, a response of type JSON, and contain an object with an array inside the data element. As our server will start with 2 elements in our contacts array we should expect a length of 2.

(Note in practice we would use a helper to avoid having to configure before each test)

app.test.js

const chai = require('chai');  
const expect = chai.expect;
const chaihttp = require('chai-http')
const app = require('../app.js');

let should = chai.should();

chai.use(chaihttp);
  
describe('API /contact', ()=>{
    it('should return all contacts', (done) =>{
        chai.request(app)
        .get('/contact')
        .end(function(err, res){
            expect(res).to.have.status(200);
            expect(res).to.be.json;

            expect(res.body).to.be.an('object');
            expect(res.body.data).to.be.an('array');

            expect(res.body.data).to.have.length(2);

            done();
        });	
    });

});

error

As expected it fails. Our job is now to make it pass by building our /contact resource endpoint.

App.js

..
let contacts = [
    {name: 'Barry', email: 'barry@barry.com'},
    {name: 'Ted', email: 'ted@barry.com'}
];

app.get('/contact', function(req, res, next){
    return res.status(200).send({ data: contacts })
});
...

For the purposes of this tutorial I will build our route methods directly in app.js. We have our contacts array containing two contacts and our /get contacts which will return the array of contacts with a 200

pass

Next we’ll add our POST contacts/ test

...
    it('Should create a new contact', (done) =>{
        chai.request(app)
        .post('/contact')
        .send({
            name: 'Bazza',
            email: 'baz@baz.com'
        })
        .end(function(err, res) {
            expect(res).to.have.status(201);
            expect(res).to.be.json;
            expect(res.body).to.be.an('object');

            expect(res.body.data).to.be.an('array');
            expect(res.body.data).to.have.length(3);

            done();
        });
    });
...

Similarly this will fail. We will now write the post route handler

...
app.post('/contact', (req, res, next) => {
    contacts.push({ name: req.body.name, email: req.body.email});

    return res.status(201).send({ data: contacts })
});
...

But what if they do not pass the correct values in the create request. We’ll now add the following test:

...
    it('Should return an error 401 when name is not passed', (done) =>{
        chai.request(app)
        .post('/contact')
        .send({
            email: 'ted@baz.com'
        })
        .end(function(err, res) {
            res.should.have.status(401);
            done();
        });	
    });
...

Running this test you will that find it fails because our router is returning ‘created 201’, and creating an element in our array without a name

We need to implement validation on our router. We’ll do this using express-validator

npm install express-validator --save
const { body,  validationResult } = require('express-validator/check');
...
app.post('/contact', [ 
        body('email').isEmail().withMessage('Email field must be an email'),
        body('name').isLength({ min: 3 }).withMessage('Name must be longer than 3 characters')
    ], (req, res, next) => {
  
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
        return res.status(401).json({ errors: errors.mapped() });
    }

    contacts.push({ name: req.body.name, email: req.body.email});
    return res.status(201).send({ data: contacts })
});
...

In this style we can iterate through our API requirements, writing our test, seeing it fail, making it pass and then refactoring if it needs be while ensuring it still passes.

On http://chaijs.com/api/ there is full documentation on the assertions that can used

Test doubles

Now suppose instead of our data being stored in an array, we had our data being stored in a database such as MongoDB. When we ran our various tests on methods such as POST/DELETE/PUT - we would potentially be making changes to the data in our database. To get around this we need to use ‘test doubles’.

A ‘test doubles’ is something which replaces the real thing, like a stunt double.

In node we can do this using Sinon.

npm install sinon sinon-mongoose --save

In sinon we have three types of ‘test double’ - Spies, Stubs and mocks.

Assuming we have an application similar to that in the ReactNodeTutorial, an API with controllers carrying out operations on a mongoDB.

/news router

router.post('/', function(req, res, next) {
    newsController.create(req.body, function(err, result){
        if(err){  
            console.log(err);
            res.json({
                success: 0,
                error: err
            })
            return;
        }

        res.status(201).json({
            success: 1,
            data: result
        });
    });

});

news controller

    create: function(params, callback){

        News.create(params, function(err, result){
            if(err){
                callback(err, null);
                return;
            }
            callback(null, result);
        });
    },

With the following test:

    it('Should create a news story', (done) =>{
        chai.request(app)
        .post('/news')
        .send({
            title: 'Owl seen swimming',
            teaser: 'This is a test teaser text about swimming owl',
            body: 'test body test body test body test body test body test body test body'
        })
        .end(function(err, res) {
            expect(res).to.have.status(201);
            expect(res).to.be.json;
            expect(res.body).to.be.an('object');

            expect(res.body).to.have.property("success");
            expect(res.body).to.have.property("data");
            expect(res.body.data).to.have.property("_id");

            done();
        });
    });

Given a situation similar to the above suppose we wanted to test that our returned _id property was correctly being added to our result, but we did not want our database creating entries.

Using sinon would could do the following:

...
const sinon = require('sinon');
const News = require('../models/News');
...
require('sinon-mongoose');

describe('API /news', ()=>{
    let sandbox = sinon.sandbox.create();;

    afterEach( (done) => {
        sandbox.restore();
        done();
    });


    it('Should create a news story', (done) =>{

        sandbox.stub(News,'create').yields(null,{_id: '3333-4444-5555'});

        chai.request(app)
        .post('/news')
        .send({
            title: 'Owl seen swimming',
            teaser: 'This is a test teaser text about swimming owl',
            body: 'test body test body test body test body test body test body test body'
        })
        .end(function(err, res) {
            if(err){
                console.log(err);
                return err;
            }
            expect(res).to.have.status(201);
            expect(res).to.be.json;
            expect(res.body).to.be.an('object');

            expect(res.body).to.have.property("success");
            expect(res.body).to.have.property("data");

            done();
        });
    });
});

Here you will see we creating a sandbox. The sandbox is the environment our doubles will operate in and will be reverted at the end of our tests via afterEach to ensure it doesn’t effect other tests.

We then stub the create method of our News schema, changing the behavior to always return the id that we wish, without any record being created

    sandbox.stub(News,'create').yields(null,{_id: '3333-4444-5555'});

Next we will move to testing our application’s front end

What is Enzyme?

Enzyme is a testing utility created by Airbnb for testing React components.

Theres going to be two parts to testing our React application. The first is testing the UI components and the second is testing our container components.

This assumes you have a React environment setup with Babel and Webpack.

It also assumes you have an understanding of Redux

First step is to install a few things we need..

npm install enzyme mocha chai chai-enzyme chai-jsx jsdom babel-register react-addons-test-utils

Then we’ll alter our package.json settings

  "scripts": {
    "start": "webpack-dev-server",
    "test": "mocha --compilers js:babel-core/register ./src/**/*.test.js"
   }
...

We should now be able to initiate our tests via ‘npm test’ on the command line

Ok, step 1 is to make a basic test work

My React app has an /src/app.js with the following React component

import React, { Component } from 'react';
import ReactDOM from 'react-dom'

export default class ReactDemo extends Component {

    render(){
        return (
            <div><h1>HELLO! - THIS IS MY REACT COMPONENT</h1></div>
        );
    }
}

ReactDOM.render(<ReactDemo />, document.getElementById('root'));

Create a tests folder in src/tests/ and within there create the file App.test.js with the following:

import React from 'react';
import chai from 'chai';
import App from '../App.js';
import Adapter from 'enzyme-adapter-react-16';    
import enzyme, {shallow} from 'enzyme';
enzyme.configure({ adapter: new Adapter() });   

let expect = chai.expect;

describe("<App/>", ()=>{
    it('Returns welcome string', ()=>{
        const wrapper = shallow(<App/>);

        expect(wrapper.find('h1')).to.have.length(1)

    });
})

running ‘npm test’, you will see that the test passes successfully as our Component contains an h1

Enzyme with redux

Ok, now we’ll develop the client for our contacts system. There will be a screen which displays a list of Contacts (Name and email)

Beneath the list will be a form to add more Contacts.

First step is for us to determine our Components

We will have a container called Contacts

This will contain a list of Contact presentation components.

Beneath them will be a CreateContact component for creating new Contacts

First we need to make our Contacts container.

create this in /src/components/containers/Contacts.js

import React, { Component} from 'react';
import ContactElement from '../presentation/ContactElement';

class Contacts extends Component {

    componentDidMount(){

    }

    render(){
        return (
            <div>
                <h1>Contacts</h1>
                <ContactElement />
            </div>
        )
    }
}

export default Contacts;

In presentation create /src/components/presentation/ContactElement.js

import React, { Component} from 'react';

const ContactElement = ({name, email}) => {
    return (
        <div>{name} {email}</div>
    )
}

export default ContactElement

As we will be using Redux - We need to install a few more packages

npm install redux redux-mock-store react-redux --save

When Enzyme renders our components it is going to need the mocked store to passed in, otherwise it will fail.

To this do this we will make the following:

import { shallow } from 'enzyme';

const shallowWithReduxStore = (component, reduxStore) => {
    const context = {
        store: reduxStore
    };
    return shallow(component, { context });
};

export default shallowWithReduxStore;

The following is how we mock our store

import React from 'react';
import chai from 'chai';
import Adapter from 'enzyme-adapter-react-16';    
import enzyme, {shallow} from 'enzyme';
enzyme.configure({ adapter: new Adapter() });   

import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import ShallowWithReduxStore from './shallowWithReduxStore.js';

import Contacts from '../components/containers/Contacts.js';
import ContactElement from '../components/presentation/ContactElement.js';

let expect = chai.expect;

describe("<Contacts/>", ()=>{
    it('Displays correct number of contacts from store', ()=>{
        const mockState = {
            contacts: {
                contacts: [
          	        { name: 'Barry', email:'barry@barry.com'},
                    { name: 'Ted', email:'ted@barry.com'}
	      	    ]
            }
        };
	    
	    const mockStore = configureStore();
	    const store = mockStore(mockState);
	    
        const wrapper = ShallowWithReduxStore(<Contacts />, store).shallow();

        expect(wrapper.find(ContactElement).length).to.equal(mockState.contacts.contacts.length)
    });
})

The test should fail…

Now lets make it pass by implementing the functionality..

import React, { Component} from 'react';
import ContactElement from '../presentation/ContactElement';
import { connect } from 'react-redux'

class Contacts extends Component {

    render(){
        const contacts = this.props.contacts.map( (contact, i) => {
            return ( <li key={i}><ContactElement name={contact.name} email={contact.email} /></li> );
        });

        return (
            <div>
                <h1>Contacts</h1>
                <ul>
                    {contacts}
                </ul>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
        contacts: state.contacts.contacts
    }
}

export default connect(mapStateToProps)(Contacts)

The test should now pass!

Next we will test our actions and our UI functions…