Unit testing is a crucial part of software development that ensures individual components of your application work as expected. In this section, we will explore how to perform unit testing in Node.js using Mocha and Chai.

What is Mocha?

Mocha is a feature-rich JavaScript test framework running on Node.js, making asynchronous testing simple and fun. It provides a variety of features such as:

  • Descriptive Test Cases: Allows you to write test cases in a readable and organized manner.
  • Asynchronous Testing: Supports asynchronous tests, making it suitable for testing Node.js applications.
  • Hooks: Provides hooks like before, after, beforeEach, and afterEach to set up preconditions and clean up after tests.

What is Chai?

Chai is a BDD/TDD assertion library for Node.js that can be paired with any JavaScript testing framework. It provides:

  • Assertions: Allows you to write assertions in a readable and expressive manner.
  • Plugins: Supports various plugins to extend its functionality.
  • Styles: Offers different assertion styles like should, expect, and assert.

Setting Up Mocha and Chai

Step 1: Install Mocha and Chai

First, you need to install Mocha and Chai using npm. Run the following commands in your project directory:

npm install --save-dev mocha chai

Step 2: Create a Test Directory

Create a directory named test in your project root to store your test files:

mkdir test

Step 3: Write Your First Test

Create a file named test/sampleTest.js and add the following code:

const { expect } = require('chai');

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      expect([1, 2, 3].indexOf(4)).to.equal(-1);
    });
  });
});

Step 4: Run Your Tests

Add a test script to your package.json file:

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

Now, run your tests using the following command:

npm test

You should see output indicating that your test has passed.

Writing More Complex Tests

Using Hooks

Hooks allow you to run code before and after tests. Here’s an example:

const { expect } = require('chai');

describe('Array', function() {
  let arr;

  beforeEach(function() {
    arr = [1, 2, 3];
  });

  describe('#push()', function() {
    it('should add a value to the end of the array', function() {
      arr.push(4);
      expect(arr).to.have.lengthOf(4);
      expect(arr[3]).to.equal(4);
    });
  });

  describe('#pop()', function() {
    it('should remove the last value from the array', function() {
      arr.pop();
      expect(arr).to.have.lengthOf(2);
      expect(arr).to.not.include(3);
    });
  });
});

Testing Asynchronous Code

Mocha supports testing asynchronous code using callbacks, promises, or async/await. Here’s an example using async/await:

const { expect } = require('chai');

describe('Async Function', function() {
  it('should return a resolved promise', async function() {
    const asyncFunction = () => Promise.resolve('Hello, World!');
    const result = await asyncFunction();
    expect(result).to.equal('Hello, World!');
  });
});

Practical Exercises

Exercise 1: Testing a Calculator Module

Create a simple calculator module and write tests for its functions.

Step 1: Create the Calculator Module

Create a file named calculator.js:

class Calculator {
  add(a, b) {
    return a + b;
  }

  subtract(a, b) {
    return a - b;
  }

  multiply(a, b) {
    return a * b;
  }

  divide(a, b) {
    if (b === 0) throw new Error('Cannot divide by zero');
    return a / b;
  }
}

module.exports = Calculator;

Step 2: Write Tests for the Calculator Module

Create a file named test/calculatorTest.js:

const { expect } = require('chai');
const Calculator = require('../calculator');

describe('Calculator', function() {
  let calculator;

  beforeEach(function() {
    calculator = new Calculator();
  });

  describe('#add()', function() {
    it('should return the sum of two numbers', function() {
      expect(calculator.add(2, 3)).to.equal(5);
    });
  });

  describe('#subtract()', function() {
    it('should return the difference of two numbers', function() {
      expect(calculator.subtract(5, 3)).to.equal(2);
    });
  });

  describe('#multiply()', function() {
    it('should return the product of two numbers', function() {
      expect(calculator.multiply(2, 3)).to.equal(6);
    });
  });

  describe('#divide()', function() {
    it('should return the quotient of two numbers', function() {
      expect(calculator.divide(6, 3)).to.equal(2);
    });

    it('should throw an error when dividing by zero', function() {
      expect(() => calculator.divide(6, 0)).to.throw('Cannot divide by zero');
    });
  });
});

Exercise 2: Testing Asynchronous Functions

Create a module with an asynchronous function and write tests for it.

Step 1: Create the Async Module

Create a file named asyncModule.js:

class AsyncModule {
  async fetchData() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve('Data fetched');
      }, 1000);
    });
  }
}

module.exports = AsyncModule;

Step 2: Write Tests for the Async Module

Create a file named test/asyncModuleTest.js:

const { expect } = require('chai');
const AsyncModule = require('../asyncModule');

describe('AsyncModule', function() {
  let asyncModule;

  beforeEach(function() {
    asyncModule = new AsyncModule();
  });

  describe('#fetchData()', function() {
    it('should return "Data fetched" after 1 second', async function() {
      const result = await asyncModule.fetchData();
      expect(result).to.equal('Data fetched');
    });
  });
});

Common Mistakes and Tips

  • Forgetting to Return Promises: When testing asynchronous code, ensure you return the promise or use async/await.
  • Not Using Hooks Properly: Use hooks like beforeEach to set up the state before each test to avoid code duplication.
  • Ignoring Edge Cases: Always test edge cases, such as dividing by zero or handling null values.

Conclusion

In this section, you learned how to set up and use Mocha and Chai for unit testing in Node.js. You wrote tests for both synchronous and asynchronous functions and explored the use of hooks to manage test state. By practicing with the provided exercises, you should now be comfortable writing and running unit tests for your Node.js applications.

Node.js Course

Module 1: Introduction to Node.js

Module 2: Core Concepts

Module 3: File System and I/O

Module 4: HTTP and Web Servers

Module 5: NPM and Package Management

Module 6: Express.js Framework

Module 7: Databases and ORMs

Module 8: Authentication and Authorization

Module 9: Testing and Debugging

Module 10: Advanced Topics

Module 11: Deployment and DevOps

Module 12: Real-World Projects

© Copyright 2024. All rights reserved