본문 바로가기
  • AI (Artificial Intelligence)
Fundamental/TTD (Test-driven development)

Mocha vs. Jest: comparison of two testing tools for Node.js

by 로샤스 2021. 7. 12.

Ref. https://www.merixstudio.com/blog/mocha-vs-jest/

Application testing is an inseparable part of product development, every developer knows that. Applying Test-Driven Development (TDD) approach supports maintaining a clean, modular, and well-prepared codebase, which tends to deliver high-quality software. JavaScript could be tested using many libraries, although two of the most popular testing tools Node.js developers use to work with are Mocha and Jest. What are the most significant differences between them, and which one should you choose? This article covers the most common use cases for unit testing, reviews, and reveals both tools’ major pros and cons.

Let's begin by introducing the two testing tools for Node.js.

Mocha is widely used in Node.js. It’s focused on various types of tests such as unit, integration, and end-to-end testing. It’s been initially designed to support the Node.js testing process.

Jest, created by Facebook developers, is mainly focused on unit testing. It’s known as a robust testing framework for React – although it can be applied to other JavaScript libraries and frameworks as well. If you want to learn more about unit testing, please refer to this article written by Sławek, where Jest plays the main testing tool’s role.

MochaJest

test runner testing framework
requires other libraries to work no preconfiguration required
offers a huge dose of flexibility regarding test development focused on simplicity
originally designed for Node.js originally designed for React

Mocha vs. Jest: main differences

At first glance, the differences between Mocha and Jest could seem insignificant; there are, however, a few points to note.

To begin with, Jest is meant to work as a single and independent testing framework, which does not require any additional dependencies. Mocha, on the other hand, needs a selection of libraries integrated together to work properly. Thus, a developer has to specify which assertion, mocking, or stubbing library is going to be used in the project. Moreover, Mocha does not specify any strict requirements for setting these libraries together and is known as a more optimized tool, which results in faster execution of tests.

Another thing worth mentioning is that Jest is generally known as an ‘easier to learn’ testing tool, which might be an asset for developers who've recently begun adopting the TDD approach. Additionally, Jest supports neither logical conjunctions nor disjunctions, whereas Mocha (together with Chai assertion library applied) does so.

Jest vs. Mocha downloads between October 2020 and April 2021

The graph above presents the most recent trends of both npm modules compared against each other. It’s clear that Jest has a way stronger position and almost doubles Mocha’s weekly downloads. On the other hand, Mocha is presented as a more stable and compact solution with a smaller number of issues. You’ve got to pick your poison, I guess.

Basic unit tests

Let’s pretend we want to write some unit tests for a new class called Person. This class contains two properties: name and age, as well as an instance method called isAdult, which simply checks if the user’s age is above or equal to 18.

// person.js class Person { constructor(name, age) { this.name = name; this.age = age; } isAdult() { return this.age >= 18; } } module.exports = Person;

For the sake of simplicity, we are not going to implement any complex examples, and combine our Mocha stack with one of the most common testing setups:

  • an assertion library called ‘Chai’,
  • a stubbing/mocking library called ‘Sinon’.

// mocha.test.js const Person = require('./person'); describe('Person unit tests', () => { let person; beforeEach(() => { person = new Person('John', 30); }); it('Should be an adult', () => { expect(person.isAdult()).to.be.true; }); it('Should be a child', () => { person.age = 12; expect(person.isAdult()).to.be.false; }); // ... });

We do not need any additional dependencies for the Jest package. This solution provides us already with assertion, stubbing, and mocking libraries working under the hood.

// jest.spec.js const Person = require('./person'); describe('Person unit tests', () => { let person; beforeEach(() => { person = new Person('John', 30); }); it('Should be an adult', () => { expect(person).toBeDefined(); expect(person.isAdult()).toBe(true); }); it('Should be a child', () => { person.age = 12; expect(person).toBeDefined(); expect(person.isAdult()).toBe(false); }); // ... });

The first noticeable difference is the syntax. Some of the common global definitions that visually do not differ from each other, such as describe or expect, could not be easily distinguished. All because in both examples they are meant to provide the same feature, but under the hood, they work in a slightly different way.

Mocha, supported by Chai, is able to handle more modularised code with logical operators such as or, and, etc.

// mocha.test.js it('Age should be between 12 and 20', () => { person.age = 16; expect(person.age).to.be.lte(20).and.to.be.gte(12); });

In Jest, several assertions have to be separated into single tasks. The syntax is a lot more concise as well.

// jest.spec.js it('Age should be between 12 and 20', () => { person.age = 16; expect(person.age).toBeGreaterThanOrEqual(12); expect(person.age).toBeLessThanOrEqual(20); });

Both tests are expected to check the same case, but the convention of achieving the results is slightly different. Combined with Chai, Mocha gives us way more flexibility regarding the structure of our assertion methods and syntax. Both implementations presented above could be also supported with custom matchers designed by a developer. It’s worth mentioning that in Chai, we’d usually try to name a matcher with no more than just a few words whereas in Jest, it could be easily named using a full sentence (usually preceded by ‘toBe’ keyword i.e. `toBeISOString`).

Mocking and stubbing data

Let’s analyze a more complex, real-life scenario, where we need to fetch a specific user from the database and check if class methods work correctly. We do not want to interact with our database, so we will stub it with a mock implementation.

While performing unit tests, dependencies such as repositories or other external services should be stubbed and mocked with results fixed in advance. This would help us not only to speed up the testing process but also to isolate our features and test each method independently from the other modules.

Assuming we have got our UserRepository imported, mocking it in Jest could be as easily done as below:

// jest.spec.ts const { UserRepository, User } = require('./user'); describe('User unit tests', () => { let userRepository; let user; beforeEach(() => { jest.restoreAllMocks(); userRepository = new UserRepository(); user = new User('John'); }); it('Should return a single user', async () => { jest.spyOn(userRepository, 'findOne').mockResolvedValue(user); const result = await userRepository.findOne('John'); expect(result).toBeInstanceOf(User); expect(result).toEqual(user); }); });

Mocha is supported by our stubbing/mocking library called ‘Sinon’ and would be implemented in the following way:

// mocha.test.js const sinon = require('sinon'); const { UserRepository, User } = require('./user'); describe('User unit tests', () => { let userRepository; let user; beforeEach(() => { sinon.restore(); userRepository = new UserRepository(); user = new User('John'); }); it('Should return a single user', async () => { sinon.stub(userRepository, 'findOne').resolves(user); expect(await userRepository.findOne('John')).to.be.instanceOf(User) .and.to.equal(user); }); });

Note that it’s not recommended to test a stubbed method, such as in the case presented above. Usually, the user should be a part of the more complex logic required to be tested. Stubbed methods would always return expected results, and testing them should not be treated as a good practice. This solution is just serving as an example of the potential implementation.

Both solutions are led by the same pattern. Methods are stubbed, a stub is called, and before each test, stubs are removed. Once again, the difference is that Jest has configured the environment for us, and Mocha uses an external library for stubbing methods. It actually does not have to be a ‘Sinon’ library, as it could be any of the other stubbing or mocking libraries available on npm repositories that meets the developer’s needs.

Which one to choose, Mocha or Jest?

Unfortunately, I won’t give you an answer to this question. It strongly depends on the type of the currently developed application. If it were a React or Nest.js application, I’d suggest sticking to the Jest framework, as it’s simply a default option. However, choosing Mocha could be the way to go for large Node.js projects thanks to its huge flexibility and variety of libraries it provides for an experienced developer. In most cases, Jest would still be a winner as it goes along with an ecosystem composed of preconfigured features such as test runner, assertion library, and other valuable tools.

It’s also worth mentioning that you do not need to stick to one or both Mocha and Jest. There are plenty of different testing tools available to use, such as Puppeteer or Jasmine – and it might turn out that they’re exactly what you need.

Bonus: testing coverage report in Jest

Jest also provides functionality to report a test code coverage ratio in our project. Simply by using ‘jest —coverage’ we can get information about how well our tests cover the whole project.

This solution can also be implemented in Mocha using test coverage libraries such as Istanbul.

Feel like taking on a new professional challenge? Join us as a Node.js Developer!

 

'Fundamental > TTD (Test-driven development)' 카테고리의 다른 글

How to Setup SonarQube locally on Mac  (0) 2023.09.13
TestDouble  (0) 2021.07.05
Contract Testing for Node.js Microservices with Pact  (0) 2021.07.05
[Mockito] Mock 개념(Mock Object)  (0) 2021.07.05
Mock 이란?  (0) 2021.07.05

댓글