Testing in Node.js Using Mocha and Chai [1/2]

Testing in Node.js Using Mocha and Chai [1/2]

Testing is a fundamental part of the software development life cycle. When you are just starting out, testing may seem boring or even redundant. However, if you want to develop software that works 100% as you expect them to and provide a good user experience to its users, testing your program properly is mandatory.

Hence, we decided to provide a simplified introduction to testing in Node.js in this post. We intend to create another post to cover more complex topics related to testing in the upcoming weeks; keep an eye on that after reading this post. (Update, the new post is now ready, Testing in Node.js Using Mocha and Chai - Part 2)

Related: [When You Should and Shouldn’t Use Node.js for Your Project]


What Is Testing?

When we talk about testing, we talk about a process that evaluates the quality of the software. Testing verifies that our program works as we intend it to work and meets the technical, functional, regulatory, and user requirements it is supposed to meet.

The approaches developers use to test applications vary according to what they are testing and at which level. Often, programs are tested using a combination of a few approaches to ensure that they are ready to be released to the world. Unit testing, integration testing, system testing, load and stress testing, and usability testing are some of the approaches commonly used today.

In this post, we are focusing on unit testing and how to write unit tests in Node.js.


Unit Testing

Unit testing is the practice of testing the smallest units or components of a software. These smallest units are identified by their independence; they are not dependent on other software components or parts. In Node.js, the smallest unit of a program is a function. Therefore, when running unit tests on Node applications, we are testing whether individual functions work as they are expected to or not.

Unit testing is the first level of software testing performed before any other type of testing. Usually, writing unit tests is the responsibility of developers.


Benefits of Unit Testing

Developers have to put an extra effort to write unit tests and testable code. Then why do experienced developers insist on doing this tedious task of unit testing? The reason is simple: its obvious benefits overweigh the cost and extra effort involved with testing.

Unit testing makes software easily maintainable. If someone makes a change to the codebase, you can run the unit tests to figure out whether the change has caused a defect in the system. If there is a defect, you can easily find where the defect is by analyzing the failed test cases.

Unit testing forces developers to write loosely coupled and reusable code. Writing tests for heavily coupled code is an impossible task. Therefore, whether you like it or not, you will have to follow these best programming practices to successfully complete unit testing. Testable code is always easier to understand because of these reasons.


Testing in Node.js—which Packages Are We Using?

Node.js has a number of packages on npm that make the process of writing tests easier. In this tutorial, we using two of the most popular Node modules available for testing: Mocha and Chai.

Mocha is the main testing framework in this test suite. It provides functions to execute tests and handle logging test results to the terminal. Chai is an assertion library commonly used with Mocha. We use assertions to verify the component that is being tested returns the value it is expected to return for a particular test case.

As you will see in the unit tests we write in this tutorial, functions like it and describe are a part of Mocha. Assertions we make inside these functions, like expect(isSuccess).to.be.true comes from the Chai library.


Set Up the Testing Environment

Before continuing, set up a new Node.js project if you don’t have one already. Then, install Mocha and Chai libraries and save them as dev-dependencies to the package.json file using this command.

npm install mocha chai --save-dev

Then, change the test script of the package.json file to this.

"scripts": {
    "test": "mocha"
 },

This allows us to run the tests using the npm test command on the command-line.

You should also create a new directory named test. This is where all our test files are going to be kept in. Note that you have to use the exact name “test” for the directory since Mocha looks for a directory with this name to run the tests.

At this point, our project directory has a folder structure like this.

|test
|src
|node_modules
package.json

Basic Test Format of Mocha

A typical test file in Mocha takes the following form.

//a collection of test cases that test a specific component
describe("validator isValid()", () => {

	//test a function for a specific case
	it("should return true for a number in between 10 and 70", ()=> {

	})

	it("should return false when the number is less than or equal to 10", () => {

	})

	it("should return false when the number is greater than or equal to 70", () => {

	})
})

Test Format with Mocha Hooks

Mocha provides hooks that we can run before and after each test case or all the test cases. They are:

  • before(): Logic inside this hook is run before all the test cases in the collection of tests.
  • after(): Logic inside this is run after all the test cases.
  • beforeEach(): Logic inside this is run before each test case in the collection.
  • afterEach(): Logic inside this is run after each test case.
describe("validator isValid()", () => {

	before(()=> {
	})

	after(()=> {	
	})

	beforeEach(()=> {
	})

	afterEach(()=> {
	})

     it("should return true for a number in between 10 and 70", ()=>   {

	})

	it("should return false when the number is less than or equal to 10", () => {

	})

	it("should return false when the number is greater than or equal to 70", () => {

	})
})

Writing Your First Unit Test

To write the first unit test using Mocha and Chai, first let’s create a simple function named isNumValid which returns true for values in between 10 and 70, and returns false for values greater than or equal to 70 and less than or equal to 10.

exports.isNumValid = function(num) {
    if (num >= 70){
        return false
    } else if (num <= 10) {
        eturn false
    } else{
        return true
    }
}

Now we can write the tests to verify this functions behavior. We create a new file inside the test directory named validator.js to write this test.

const chai = require('chai')
const expect = chai.expect

const validator = require('../app/validator')

describe("validator isNumValid()", () => {

	it("should return true for a number in between 10 and 70", ()=> {
		expect(validator.isNumValid(39)).to.be.true
	})

	it("should return false when the number is less than or equal to 10", () => {
		expect(validator.isNumValid(10)).to.be.false
	})

	it("should return false when the number is greater than or equal to 70", () => {
		expect(validator.isNumValid(79)).to.be.false
	})
})

You have to import the validator.js file to our test file in order to access the function being tested. In our case, the function is written in the validator.js file inside the src directory.

In addition to the assertion style, expect, we have used here, Chai provides two other assertion styles, should and assert. You can read more about the differences and use cases of these styles in the chai assertion styles guide.

Run the test

Use the command npm test to run these tests. Mocha outputs the results of the tests and shows them on the command-line.

Test passed successful output

Test passed successful output

Failing Test Case

Assume that we have made a mistake when writing the isNumValid function. We have forgotten to add the equal condition with the greater than (>) and less than (<) operators.

exports.isNumValid = function(num) {
    if (num > 70) {
        return false
    } else if (num < 10) {
        return false
    } else {
        return true
    }
}

When we run the tests again, one test case fails due to this mistake.

Test failed output

Test failed output

Because of unit testing, we are able to catch and correct mistakes like this easily without having to pour hours into finding where the bug is.

However, in the above scenario, only one test case failed even though we had made the same mistake with both boolean operators. This is a problem. But is this a shortcoming of unit testing?

No, it’s a shortcoming in our test cases. We haven’t considered all the input scenarios and written separate test cases for each of them. We have combined the boundary conditions (when the number is equal to 10 and 70) with other failing test conditions instead of writing  separate test case for each condition.

Therefore, when following proper unit testing practices, our test suite should contain five test cases in total.

const chai = require('chai')
const expect = chai.expect

const validator = require('../app/validator')

describe("validator isNumValid()", () => {

	it("should return true for a number in between 10 and 70", ()=> {
		expect(validator.isNumValid(39)).to.be.true
	})

	it("should return false when the number is less 10", () => {
		expect(validator.isNumValid(9)).to.be.false
	})

	it("should return false when the number is equal to 10", () => {
		expect(validator.isNumValid(10)).to.be.false
	})

	it("should return false when the number is equal to 70", () => {
		expect(validator.isNumValid(70)).to.be.false
	})

	it("should return false when the number is greater than 70", () => {
		expect(validator.isNumValid(71)).to.be.false
	})
})

Conclusion

In this post, we gave a quick and simplified introduction to testing, unit testing, and testing in Node.js. However, this tutorial contained only the basics concepts involved with testing. In the next post, we going further into the topic of testing in Node.js. We will discuss about writing complex test cases, integration testing and other testing practices like using mocks and stubs.

[Related: A Complete Introduction to Node Buffers]

Thanks for reading!

Get new articles to your inbox

Thousands of other developers from companies like Google, Meta, Siemens, freelancers and entrepreneurs are already learning with me.