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
, andafterEach
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
, andassert
.
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:
Step 2: Create a Test Directory
Create a directory named test
in your project root to store your test files:
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:
Now, run your tests using the following command:
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
- Introduction to NPM
- Installing and Using Packages
- Creating and Publishing Packages
- Semantic Versioning
Module 6: Express.js Framework
- Introduction to Express.js
- Setting Up an Express Application
- Middleware
- Routing in Express
- Error Handling
Module 7: Databases and ORMs
- Introduction to Databases
- Using MongoDB with Mongoose
- Using SQL Databases with Sequelize
- CRUD Operations
Module 8: Authentication and Authorization
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with Mocha and Chai
- Integration Testing
- Debugging Node.js Applications
Module 10: Advanced Topics
Module 11: Deployment and DevOps
- Environment Variables
- Using PM2 for Process Management
- Deploying to Heroku
- Continuous Integration and Deployment