Testing Your Express.js Application

Write unit and integration tests for your Express.js application using frameworks like Mocha and Chai.


Mastering Express.js: Testing Your Application

Testing Your Express.js Application

Testing is a crucial part of developing robust and reliable Express.js applications. It helps you identify bugs early, ensures your code behaves as expected, and facilitates future refactoring. Without testing, you risk deploying code that doesn't work correctly, leading to poor user experience and potential data corruption. Testing in Express.js can be categorized into different types, each with its own purpose:

  • Unit Tests: Focus on testing individual components or functions in isolation. They verify that each unit of code performs its intended function correctly.
  • Integration Tests: Verify that different parts of your application work together correctly. This often involves testing interactions between routes, middleware, and database queries.
  • End-to-End (E2E) Tests: Simulate real user scenarios by testing the entire application flow from start to finish. These are typically used for high-level functionality verification.

Unit and Integration Tests with Mocha and Chai

Mocha and Chai are popular JavaScript testing frameworks that are commonly used with Express.js. Mocha provides the testing structure and runner, while Chai provides assertion libraries for verifying expected results.

Setting up Mocha and Chai

First, you'll need to install Mocha and Chai as development dependencies in your project:

npm install --save-dev mocha chai supertest

We also include `supertest` which is a library for testing HTTP requests.

Writing Unit Tests

Let's consider a simple example: Suppose you have a utility function that formats a date.

// utils/dateFormatter.js
function formatDate(date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

module.exports = { formatDate };

Here's how you might write a unit test for this function:

// test/unit/dateFormatter.test.js
const { formatDate } = require('../../utils/dateFormatter');
const { expect } = require('chai');

describe('dateFormatter', () => {
  it('should format a date correctly', () => {
    const date = new Date(2023, 10, 20); // November 20, 2023
    const formattedDate = formatDate(date);
    expect(formattedDate).to.equal('2023-11-20');
  });

  it('should handle single-digit months and days', () => {
    const date = new Date(2023, 0, 5); // January 5, 2023
    const formattedDate = formatDate(date);
    expect(formattedDate).to.equal('2023-01-05');
  });
});

Explanation:

  • `describe`: Groups related tests together.
  • `it`: Defines a single test case.
  • `expect`: A Chai assertion that allows you to express what you expect the result to be.

Writing Integration Tests

Now, let's consider an Express.js route that returns a user:

// app.js (simplified)
const express = require('express');
const app = express();
const port = 3000;

app.get('/users/:id', (req, res) => {
  const userId = parseInt(req.params.id);
  // In a real application, you would fetch the user from a database.
  const users = [{id: 1, name: "John"},{id: 2, name: "Jane"}];
  const user = users.find(u => u.id === userId);

  if (user) {
    res.json(user);
  } else {
    res.status(404).send('User not found');
  }
});

module.exports = app;  // Export the app for testing.
//app.listen(port, () => console.log(`Example app listening on port ${port}!`)); // Don't start the server when exporting

Here's an integration test using Supertest:

// test/integration/users.test.js
const request = require('supertest');
const app = require('../../app'); // Import your Express app
const { expect } = require('chai');

describe('GET /users/:id', () => {
  it('should return a user if the ID is valid', (done) => {
    request(app)
    .get('/users/1')
    .expect(200)
    .end((err, res) => {
      if (err) return done(err);
      expect(res.body).to.deep.equal({ id: 1, name: 'John' });
      done();
    });
});

it('should return 404 if the user is not found', (done) => {
  request(app)
    .get('/users/999')
    .expect(404)
    .end((err, res) => {
      if (err) return done(err);
      expect(res.text).to.equal('User not found');
      done();
    });
});

});

Explanation:

  • `request(app)`: Creates a Supertest agent to send HTTP requests to your Express app.
  • `.get('/users/1')`: Sends a GET request to the specified route.
  • `.expect(200)`: Asserts that the response status code is 200 (OK).
  • `.end((err, res) => { ... })`: Handles the response and performs further assertions. The `done()` callback signals to Mocha that the asynchronous test is complete.
  • `expect(res.body).to.deep.equal({ id: 1, name: 'John' })`: Asserts that the response body matches the expected user object.

Running the Tests:

Add a `test` script to your `package.json`

"scripts": {
    "test": "mocha test/**/*.test.js --exit"
  }

Then run the tests using:

npm test