Converting your Unit and End-To-End Test Suites from Mocha, Chai, and Sinon to Jest in Nodejs.
Tags: node, jest, mocha, test
Published: 2020-03-22 21:44:59

---
title: Converting your Unit and End-To-End Test Suites from Mocha, Chai, and Sinon to Jest in Nodejs.
published: true
description: 
tags: #nodejs #jest #mocha #test
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/dtbn7152xnh8i9wu57vc.jpg
---

I started on a project recently and Jest is a requirement for testing. Making the switch from what I am already used to(mocha, chai, and sinon) is not difficult though, I wish to explain in this article some of the differences I observed using code samples. 

###Mocha
[Mocha](https://mochajs.org) is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting while mapping uncaught exceptions to the correct test cases. In other words, mocha is a javascript test framework.

###Chai
[Chai](https://www.chaijs.com) is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.

###Sinon
[Sinon](https://sinonjs.org) provides standalone test spies, stubs and mocks for JavaScript. 

###Jest
[Jest](https://jestjs.io) is a delightful JavaScript Testing Framework with a focus on simplicity.


###**Mocha or Jest?**

Both Mocha and Jest are both javascript test frameworks(test runners).
A vivid comparison between both mocha and jest is found [here](https://blog.usejournal.com/jest-vs-mocha-whats-the-difference-235df75ffdf3).

Jest comes with built-in mocking and assertion abilities. In addition, Jest runs your tests concurrently in parallel, providing a smoother, faster test run. There’s no upfront configuration that you have to do. You just install it through npm or yarn, write your test, and run **jest**. Get the full details [here](https://blog.usejournal.com/jest-vs-mocha-whats-the-difference-235df75ffdf3).

Mocha provides developers with a base test framework, allowing you to have options as to which assertion, mocking, and spy libraries you want to use.
This does require some additional setup and configuration, which is a downside. However, if having complete control of your testing framework is something you want, Mocha is by far the most configurable and best choice. Get the full details [here](https://blog.usejournal.com/jest-vs-mocha-whats-the-difference-235df75ffdf3).

What we could deduce from the above explanation is that when using **Jest**, you have most of the tools that are needed both for your unit and end to end tests, such as assertion and mocking abilities, while when using **Mocha**, you will need to require external libraries for assertion and mocking. So, Chai can be used for assertions while Sinon can be used for mocking.

I have no problem using Jest alone or using Mocha along with Chai and Sinon. My use case is wholely dependent on the project requirement. 

##**The project**
I built a Mock Premier League Fixture API so as to demonstrate how you can use either **jest** or **mocha**. You can check out the code on github.
Jest is used in the [master](https://github.com/victorsteven/Premier-League/tree/master) branch, while Mocha/Chai/Sinon are used in the [mocha-chai-sinon](https://github.com/victorsteven/Premier-League/tree/mocha-chai-sinon) branch.

Get the full code:
Using Jest [here](https://github.com/victorsteven/Premier-League/tree/master).
Using mocha [here](https://github.com/victorsteven/Premier-League/tree/mocha-chai-sinon).

###**Test Setup**
An in-memory database is used for the unit tests while a real test database is used for the end-to-end tests. Mongodb is used as the database in this project.

####**Jest Setup**
This is only for jest use case.
First, install jest and @shelf/jest-mongodb and supertest(used for end-to-end tests)
```
npm install --save-dev jest supertest @shelf/jest-mongodb 
```
Then we create a **jest.config.js** file in the root directory and specify the preset.

```javascript
module.exports = {
  preset: '@shelf/jest-mongodb',
};
```
Next we, create **jest-mongodb-config.js** file which is used to configure our in-memory db for unit tests:
```javascript
module.exports = {
  mongodbMemoryServerOptions: {
    instance: {
      dbName: 'jest'
    },
    binary: {
      version: '4.0.2', // Version of MongoDB
      skipMD5: true
    },
    autoStart: false
  }
};
```
We then need to setup database and seed data. Create the **test-setup** directory and the **db-config.js** and **seed.js** files

The **db-config.js** file looks like this: 

```javascript

import mongoose from 'mongoose'


//in-memory db used only in unit testing
export const connect = async () => {
  const mongooseOpts = {
    useNewUrlParser: true,
    autoReconnect: true,
    reconnectTries: Number.MAX_VALUE,
    reconnectInterval: 1000
  };
  await mongoose.connect(global.__MONGO_URI__, mongooseOpts)
};

//Drop database, close the connection. 
//Used by both unit and e2e tests
export const closeDatabase = async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
};


//Remove all the data for all db collections. 
//Used by both unit and e2e tests
export const clearDatabase = async () => {
  const collections = mongoose.connection.collections;
  for (const key in collections) {
      const collection = collections[key];
      await collection.deleteMany();
  }
};

```

The file above is self-explanatory. You can checkout out the seed.js file in the [repo](https://github.com/victorsteven/Premier-League)

The last setup using jest is to specify the script to run in the **package.json** file:
```json
    "test": "cross-env NODE_ENV=test jest --runInBand  --testTimeout=20000"
```
**cross-env** enable us run scripts that set and use environment variables across platforms. As seen above, it enabled us set our environment to **test**. Install using: 
```
npm install cross-env
```
To disable concurrency (parallel execution) in Jest, we specify the **runInBand** flag so as to make Jest run tests sequentially.
We then specified a timeout of 20 seconds (20000ms).

Specify a key in the **package.json** file to tell jest about the test environment, files to ignore while testing and that test output should be in verbose.

```json
"jest": {
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": [
      "/node_modules/",
      "/dist/"
    ],
    "verbose": true
  },
```

####**Mocha, Chai and Sinon Setup**
This is for Mocha, Chai, and Sinon users.
First, install mocha, chai and sinon, and their extensions that will be used in the unit and end-to-end tests
```
npm install --save-dev mocha chai chai-as-promised chai-http sinon @sinonjs/referee-sinon sinon-chai  
```
For unit testing, we will need to install a mongodb memory server:
```
npm install mongodb-memory-server --save-dev
```
We then install **nyc** which is the **Istanbul** command-line interface for code coverage:
```
npm install nyc --save-dev
```
We next setup database and seed data. Create the **test-setup** directory and the **db-config.js**
The content of the **db-config.js** file:
```javascript

import mongoose from 'mongoose'
import { MongoMemoryServer } from 'mongodb-memory-server'

const mongod = new MongoMemoryServer();

//in-memory db for unit test
export const connect = async () => {
  const uri = await mongod.getConnectionString();
    const mongooseOpts = {
      useNewUrlParser: true,
      autoReconnect: true,
      reconnectTries: Number.MAX_VALUE,
      reconnectInterval: 1000
    };

  await mongoose.connect(uri, mongooseOpts);

};

//works perfectly for unit test in-memory db
export const closeDatabase = async () => {
    await mongoose.connection.dropDatabase(); 
    await mongoose.connection.close();
};


//Remove all the data for all db collections. 
export const clearDatabase = async () => {
  const collections = mongoose.connection.collections;
  for (const key in collections) {
      const collection = collections[key];
      await collection.deleteMany();
  }
};
```

We use the  **mongodb-memory-server** library to setup in-memory db for unit tests. This can also be used for **jest** but we followed a different approach, as seen in the **jest setup**.

Next, create the **mocha.env.js** file which is used to tell our test the environment to run on. We used **cross-env** to take care of this in the jest configuration above. I tried using that with mocha, but I didn't give the desired result.
So the **mocha.env.js** file:
```javascript
process.env.NODE_ENV = 'test';
```
Then, the script file in package.json, where we will require the above file, use babel to convert ES6 to ES5, specify the directories mocha will look for when running our tests and set a timeout of 20seconds.

```json
"test": "nyc --require @babel/register --require ./mocha.env.js  mocha ./api/**/*.test.js --timeout 20000 --exit"
```


###**An Example**
Remember to stick to using one test framework(jest or mocha) per project.

Let's consider the signup/create user flow.
We have the **user.controller.js** file:
```javascript
import User from '../models/user'
import validate from '../utils/validate'

class UserController {
  constructor(userService){
    this.userService = userService
  }
  async createUser(req, res) {
    const errors = validate.registerValidate(req)
    if (errors.length > 0) {
      return res.status(400).json({
        status: 400,
        errors: errors
      })
    }
    const { name, email, password } =  req.body
    
    let user = new User({
      name: name.trim(),
      email: email.trim(),
      password: password.trim(),
    })
    try {
      const createUser = await this.userService.createUser(user)
      return res.status(201).json({
        status: 201,
        data: createUser
      })
    } catch(error) {
      return res.status(500).json({
        status: 500,
        error: error.message
      })
    }
  }
}

export default UserController

```
We took the user's input from the request, called the **registerValidate** function from the **validate.js** file located in the **utils** directory in the [repo](https://github.com/victorsteven/Premier-League), we then called the **createUser** method passing in the user to create. **createUser** is a method defined in the  **user.service.js** file, which is passed into our controller using dependency injection. 

The **user.service.js** file looks like this:
```javascript
import User from '../models/user'
import password from '../utils/password';

class UserService {
  constructor() {
    this.user = User
  }
  async createUser(user) {
    try {
      //check if the user already exists
      const record = await this.user.findOne({ email: user.email })
      if (record) {
        throw new Error('record already exists');
      }
      user.password = password.hashPassword(user.password)
      //assign role:
      user.role = "user"
      //create the user
      const createdUser = await this.user.create(user);
      const { _id, name, role } = createdUser;
      //return user details except email and password:
      const publicUser = { 
        _id,
        name,
        role
      }
      return publicUser
    } catch(error) {
      throw error;
    }
  }
}

export default UserService

```


###**Unit Tests**

Let's now wire our test cases for the files above.
To achieve unit test, we will need to mock external function/method calls.
From the **user.controller.js** file above, the **createUser** controller method we will mock the calls to **registerValidate** function, **createUser** service method, the response and the status that is sent back to the client.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eigacanc4pzsgbomin8e.png)
Looking at the **user.service.js** file, the createUser service method called an external function, **hashPassword** to help us hash the password. To achieve unit testing, we will mock that.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6c12px2jwmxyxs6ki799.png)


####**Using Jest**
a. Controller **createUser** method.
To mock the response and the status, we will use **jest.fn()**, which is used to create a jest mock object.
We use **jest.spyOn** to mock the **registerValidate** and **createUser** methods. It is used to mock just a function/method in a given object or class.

The **user.controller.test.js** file:
```javascript
import faker from 'faker'
import validate from '../utils/validate'
import UserController from './user.controller'
import UserService from '../services/user.service'

const mockResponse = () => {
  const res = {};
  res.status = jest.fn().mockReturnValue(res);
  res.json = jest.fn().mockReturnValue(res);
  return res;
};

describe('UserController', () => {
  describe('createUser', () => {
    let userController, userService, res;

    beforeEach(() => {
      res = mockResponse()
      userService = new UserService();
    });
    afterEach(() => {    
      jest.clearAllMocks();
    });

    it('should create a user successfully', async () => {
      const req = {
        body: { name: faker.name.findName(), email: faker.internet.email(), password: faker.internet.password() }
      };
      //since validate is foreign, we have to mock it to achieve unit test. We are only mocking the 'registerValidate' function
      const errorStub = jest.spyOn(validate, 'registerValidate').mockReturnValue([]); //no input error
      const stubValue = {
        name: faker.name.findName(),
      };
      //We also mock the 'createUser' service method
      const stub = jest.spyOn(userService, 'createUser').mockReturnValue(stubValue);

      userController = new UserController(userService);

      await userController.createUser(req, res);

      expect(errorStub).toHaveBeenCalledTimes(1)
      expect(stub).toHaveBeenCalledTimes(1)
      expect(res.status).toHaveBeenCalledTimes(1);
      expect(res.json).toHaveBeenCalledTimes(1);
      expect(res.status).toHaveBeenCalledWith(201);
      expect(res.json).toHaveBeenCalledWith({'status': 201, 'data': stubValue});
    });
  });
});

```
You can check out the [repo](https://github.com/victorsteven/Premier-League) for unsuccessful user creation tests.
So, we tested only the **createUser** controller method and mocked all other methods that it depended on, with the help of jest mock and spies libraries. So we can say that the **createUser** controller method is unit tested🔥. 

b. Service **createUser** method.
Instead of hitting a real database, we will use the in-memory database we had earlier set up in order to achieve unit tests in the services.

The **user.service.test.js** file:

```javascript
import UserService from './user.service'
import  password from '../utils/password';
import { seedUser } from '../test-setup/seed'
import  { connect, clearDatabase, closeDatabase  }  from '../test-setup/db-config'


let seededUser

//Connect to in-memory db before test
beforeAll(async () => {
  await connect();
});
beforeEach(async () => {
  seededUser = await seedUser()
});
// Clear all test data after every test.
afterEach(async () => {
  await clearDatabase();
});
// Remove and close the db and server.
afterAll(async () => {
  await closeDatabase();
});


describe('UserService', () => {
  describe('createUser', () => {
    it('should not create a new user if record already exists', async () => {
      let user = {
        name: 'frank',
        email: seededUser.email,
        password: 'password',
      }
      const userService = new UserService();

      await expect(userService.createUser(user)).rejects.toThrow('record already exists'); 
    });

    it('should create a new user', async () => {
      let userNew = {
        name: 'kate',
        email: 'kate@example.com',
        password: 'password',
      }

      //'hashPassword' is a  dependency, so we mock it, and return any value we want
      const hashPass = jest.spyOn(password, 'hashPassword').mockReturnValue('ksjndfklsndflksdmlfksdf')

      const userService = new UserService();
      const user = await userService.createUser(userNew);

      expect(hashPass).toHaveBeenCalled();
      expect(user._id).toBeDefined();
      expect(user.name).toBe(userNew.name);
      expect(user.role).toBe(userNew.role);
    });
  });

```
We have both a failure and a successful test case. For the failure test, we first seeded our in-memory db with a user, then tried to insert a record that has the same email as the seeded user. We expected that test to throw an error, which it did:
```javascript
   await expect(userService.createUser(user)).rejects.toThrow('record already exists'); 
```
We also tested for a successful insertion.
 

####**Using Mocha/Chai/Sinon**
We will mock external methods and functions using sinon's **stub**.

a. Controller **createUser** method.
The **user.controller.test.js** file will look like this:

```javascript
import chai from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import faker from 'faker'
import validate from '../utils/validate'
import UserController from './user.controller'
import UserService from '../services/user.service'

chai.use(require('chai-as-promised'))
chai.use(sinonChai)
const { expect } = chai

const mockResponse = () => {
  const res = {};
  res.status = sinon.stub()
  res.json = sinon.stub()
  res.status.returns(res);
  return res;
};

describe('UserController', () => {
  let userController, userService, res, sandbox = null;
  beforeEach(() => {
    sandbox = sinon.createSandbox()
    res = mockResponse()
    userService = new UserService();
  });
  afterEach(() => {
    sandbox.restore()
  })

  describe('createUser', () => {
    it('should create a user successfully', async () => {
      const req = {
        body: { name: faker.name.findName(), email: faker.internet.email(), password: faker.internet.password() }
      };
      //since validate is foreign, we have to mock it to achieve unit test. We are only mocking the 'registerValidate' function
      const errorStub = sandbox.stub(validate, 'registerValidate').returns([]); //no input error

      const stubValue = {
        name: faker.name.findName(),
      };
      const stub = sandbox.stub(userService, 'createUser').returns(stubValue);

      userController = new UserController(userService);
      await userController.createUser(req, res);

      expect(errorStub.calledOnce).to.be.true;
      expect(stub.calledOnce).to.be.true;
      expect(res.status.calledOnce).to.be.true;;
      expect(res.json.calledOnce).to.be.true;;
      expect(res.status).to.have.been.calledWith(201);
      expect(res.json).to.have.been.calledWith({'status': 201, 'data': stubValue});

    });
  });
});

```
As seen above, the beforeEach() hook, we created a sinon sandbox. Sandboxes remove the need to keep track of every fake created, which greatly simplifies cleanup. It becomes useful when other tests are added, as shown in the [repository](https://github.com/victorsteven/Premier-League/blob/master/api/controllers/user.controller.test.js).

b. Service **createUser** method
The **user.service.test.js** file will look like this:

```javascript

import chai from 'chai'
import sinon from 'sinon'
import UserService from './user.service'
import  password from '../utils/password';
import { seedUser } from '../test-setup/seed'
import  { connect, clearDatabase, closeDatabase  }  from '../test-setup/db-config'

chai.use(require('chai-as-promised'))
const { expect } = chai

describe('UserService', () => {

  let seededUser, sandbox = null
 
  //Connect to in-memory db 
  before(async () => {
    await connect();
  });
  beforeEach(async () => {
    seededUser = await seedUser()
    sandbox = sinon.createSandbox()
  });
  //Clear all test data after every test.
  afterEach(async () => {
    await clearDatabase();
    sandbox.restore()
  });
  //Remove and close the db and server.
  after(async () => {
    await closeDatabase();
  });

  describe('createUser', () => {
    it('should not create a new user if record already exists', async () => {

      let user = {
        name: 'frank',
        email: seededUser.email,
        password: 'password',
      }
      const userService = new UserService();
      await expect(userService.createUser(user)).to.be.rejectedWith(Error, 'record already exists')
    });

    it('should create a new user', async () => {

      let userNew = {
        name: 'kate',
        email: 'kate@example.com',
        password: 'password',
      }

      //'hashPassword' is a  dependency, so we mock it
      const hashPass = sandbox.stub(password, 'hashPassword').returns('ksjndfklsndflksdmlfksdf')

      const userService = new UserService();
      const user = await userService.createUser(userNew);

      expect(hashPass.calledOnce).to.be.true;
      expect(user._id).to.not.be.undefined
      expect(user.name).to.equal(userNew.name);
      expect(user.role).to.equal(userNew.role);
    });
  });
});

```
You can see that we have two tests in the above suite. One failure and one success. For the failure test, we seeded our in-memory db and tried to add a record with the same email like the one in the db. You might need to pay attention to this line:
```javascript
await expect(userService.createUser(user)).to.be.rejectedWith(Error, 'record already exists')
```
We expected the promise to be rejected with an error. This was made possible using:
```javascript
chai.use(require('chai-as-promised'))
```
We have used the create user functionality to see how we can write unit tests in our **controllers** and **services**, using either **jest** or **mocha** test framework. Do well to check the [repo](https://github.com/victorsteven/Premier-League) for the entire test suites.


###**End To End Tests(e2e)**
For our e2e tests, we won't be mocking any dependency. We want to really test an entire functionality that cuts across different layers at a goal. This is essential as help gives us confidence that all layers in our api as working as expected. We will only see an example when **jest** is used.  You can check the **mocha-chai-sinon** [branch](https://github.com/victorsteven/Premier-League/tree/mocha-chai-sinon) for e2e tests using **mocha**.

The entire e2e tests inside the e2e_tests directory:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1s593no2rwvo9y93zb86.png)

A couple of things to note, we will use the supertest installed earlier in our e2e tests. We also use a real test database. You can check the db configuration in the **database** directory from the [repository](https://github.com/victorsteven/Premier-League).

####**User e2e test**

```javascript
import supertest from 'supertest'
import app from '../app/app'
import http from 'http'
import User from '../models/user'
import { seedUser } from '../test-setup/seed'
import  { clearDatabase, closeDatabase  }  from '../test-setup/db-config'


let server, request, seededUser

beforeAll(async () => {
  server = http.createServer(app);
  await server.listen();
  request = supertest(server);
});
beforeEach(async () => {
    seededUser = await seedUser()
});
//Clear all test data after every test.
afterEach(async () => {
  await clearDatabase();
});
//Remove and close the test db and server.
afterAll(async () => {
  await server.close();
  await closeDatabase();
});


describe('User E2E', () => {
  describe('POST /user', () => {
    it('should create a user', async () => {
      let user = {
        name: 'victor',
        email: 'victor@example.com',
        password: 'password'
      }
      const res = await request
                        .post('/api/v1/users')
                        .send(user)

      const { _id, name, role } = res.body.data

      //we didnt return email and password, so we wont assert for them
      expect(res.status).toEqual(201);
      expect(_id).toBeDefined();
      expect(name).toEqual(user.name);
      expect(role).toEqual('user');

      //we can query the db to confirm the record
      const createdUser = await User.findOne({email: user.email })
      expect(createdUser).toBeDefined()
      expect(createdUser.email).toEqual(user.email);
      //since our password is hashed:
      expect(createdUser.password).not.toEqual(user.password);
    });

    it('should not create a user if the record already exist.', async () => {
      let user = {
        name: 'chikodi',
        email: seededUser.email, //a record that already exist
        password: 'password'
      }
      const res = await request
                        .post('/api/v1/users')
                        .send(user)

      expect(res.status).toEqual(500);
      expect(res.body.error).toEqual('record already exists');
    });


    it('should not create a user if validation fails', async () => {
      let user = {
        name: '', //the name is required
        email: 'victorexample.com', //invalid email
        password: 'pass' //the password should be atleast 6 characters
      }
      const res = await request
                        .post('/api/v1/users')
                        .send(user)

      const errors =  [ 
        { name: 'a valid name is required' },
        {email: 'a valid email is required'},
        { password: 'a valid password with atleast 6 characters is required' } 
      ]                  
      expect(res.status).toEqual(400);
      expect(res.body.errors).toEqual(errors);
    });
  });
});
```
From the above, we have two failure tests and one successful test case.
We created a fake server so that we don't listen to the real server and mess it up. After the test, we close the fake server.
You can check how this test is done using mocha, chai, and chai-http from the [mocha-chai-sinon](https://github.com/victorsteven/Premier-League/tree/mocha-chai-sinon) branch.

A sample output of the project entire test suites:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/u6x7n1h5dn4vhxhtm03t.png)


##**Conclusion**
With a few examples, we have explored use cases when using **jest** and **mocha**. These are some of my findings:
a. Declaring test hooks can be defined both inside and outside the **describe** block when using **jest**. This is not the case when using mocha, as test hooks are defined inside a **describe** block.
b. Jest has **instabul** built it for test coverage by using the **--coverage** flag when running tests. This is not the case with **mocha** which requires an external package **nyc**(which is Istanbul command line interface) for test coverage.
c. Jest has most of the test tools built-in, hence you can hit the ground running immediately. Mocha provides you a base test framework and allows you to use libraries of your choice for assertions, spies, and mocks.

Get the full code:
Using Jest [here](https://github.com/victorsteven/Premier-League/tree/master).
Using mocha [here](https://github.com/victorsteven/Premier-League/tree/mocha-chai-sinon).

Happy Testing.

You can follow on [twitter](https://twitter.com/stevensunflash) for new notifications.