DEV Community

Mohin Sheikh
Mohin Sheikh

Posted on

Writing Unit Tests for an Express.js Application: A Beginner-Friendly Guide

Unit Tests


📖 Table of Contents

  1. Introduction to Unit Testing
  2. Why Unit Testing is Important
  3. Setting Up the Testing Environment
  4. Installing Testing Dependencies
  5. Project Structure for Testing
  6. Writing Unit Tests for Controllers
  7. Writing Unit Tests for Middleware
  8. Writing Unit Tests for Services
  9. Running Unit Tests
  10. Best Practices for Unit Testing
  11. Conclusion

1. Introduction to Unit Testing

Unit testing involves testing individual units or components of an application in isolation to ensure they work as expected. In Express.js, unit tests typically focus on testing controllers, middlewares, and services.


2. Why Unit Testing is Important

  • Detects bugs early in development.
  • Ensures code behaves as expected.
  • Makes refactoring easier.
  • Improves code maintainability.
  • Provides documentation for how the code should work.

3. Setting Up the Testing Environment

Ensure your Express.js application is set up and functional.

If not, follow the Setup Guide.


4. Installing Testing Dependencies

Install the required testing libraries:

npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
Enter fullscreen mode Exit fullscreen mode
  • jest: Testing framework.
  • ts-jest: Jest configuration for TypeScript.
  • supertest: Library for testing HTTP APIs.

4.1 Configure Jest

Create a jest.config.ts file in your project root:

export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  moduleFileExtensions: ['ts', 'js', 'json'],
  testMatch: ['**/__tests__/**/*.test.ts'],
};
Enter fullscreen mode Exit fullscreen mode

Add a test script to your package.json:

{
  "scripts": {
    "test": "jest --coverage"
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Project Structure for Testing

src/
├── controllers/
│   ├── userController.ts
├── services/
│   ├── userService.ts
├── middleware/
│   ├── authMiddleware.ts
└── __tests__/
    ├── controllers/
    │   ├── userController.test.ts
    ├── services/
    │   ├── userService.test.ts
    ├── middleware/
        ├── authMiddleware.test.ts
Enter fullscreen mode Exit fullscreen mode

6. Writing Unit Tests for Controllers

6.1 Example Controller

File: src/controllers/userController.ts

import { Request, Response } from 'express';

export const getUser = (req: Request, res: Response) => {
  res.status(200).json({ id: 1, name: 'John Doe' });
};
Enter fullscreen mode Exit fullscreen mode

6.2 Test Controller

File: src/__tests__/controllers/userController.test.ts

import { getUser } from '../../controllers/userController';
import { Request, Response } from 'express';

describe('User Controller - getUser', () => {
  it('should return user data with status 200', () => {
    const req = {} as Request;
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    } as unknown as Response;

    getUser(req, res);

    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalledWith({ id: 1, name: 'John Doe' });
  });
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Mocked Request and Response.
  2. Called the getUser controller function.
  3. Asserted status code and response data.

7. Writing Unit Tests for Middleware

7.1 Example Middleware

File: src/middleware/authMiddleware.ts

import { Request, Response, NextFunction } from 'express';

export const isAuthenticated = (req: Request, res: Response, next: NextFunction) => {
  if (req.headers.authorization === 'Bearer validToken') {
    next();
  } else {
    res.status(403).json({ message: 'Forbidden' });
  }
};
Enter fullscreen mode Exit fullscreen mode

7.2 Test Middleware

File: src/__tests__/middleware/authMiddleware.test.ts

import { isAuthenticated } from '../../middleware/authMiddleware';
import { Request, Response, NextFunction } from 'express';

describe('Auth Middleware - isAuthenticated', () => {
  it('should call next if token is valid', () => {
    const req = { headers: { authorization: 'Bearer validToken' } } as Request;
    const res = {} as Response;
    const next = jest.fn();

    isAuthenticated(req, res, next);

    expect(next).toHaveBeenCalled();
  });

  it('should return 403 if token is invalid', () => {
    const req = { headers: { authorization: '' } } as Request;
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    } as unknown as Response;
    const next = jest.fn();

    isAuthenticated(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({ message: 'Forbidden' });
  });
});
Enter fullscreen mode Exit fullscreen mode

8. Writing Unit Tests for Services

8.1 Example Service

File: src/services/userService.ts

export const findUserById = (id: number) => {
  if (id === 1) {
    return { id: 1, name: 'John Doe' };
  }
  return null;
};
Enter fullscreen mode Exit fullscreen mode

8.2 Test Service

File: src/__tests__/services/userService.test.ts

import { findUserById } from '../../services/userService';

describe('User Service - findUserById', () => {
  it('should return user data when valid ID is provided', () => {
    const user = findUserById(1);
    expect(user).toEqual({ id: 1, name: 'John Doe' });
  });

  it('should return null when invalid ID is provided', () => {
    const user = findUserById(2);
    expect(user).toBeNull();
  });
});
Enter fullscreen mode Exit fullscreen mode

9. Running Unit Tests

Run your tests with:

npm run test
Enter fullscreen mode Exit fullscreen mode

Sample Output:

 PASS  __tests__/controllers/userController.test.ts
 PASS  __tests__/middleware/authMiddleware.test.ts
 PASS  __tests__/services/userService.test.ts

Test Suites: 3 passed, 3 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        2.345 s
Enter fullscreen mode Exit fullscreen mode

Check Test Coverage

npm run test -- --coverage
Enter fullscreen mode Exit fullscreen mode

10. Best Practices for Unit Testing

  1. Test one functionality per test case.
  2. Mock external dependencies (e.g., database calls).
  3. Ensure your tests are independent and isolated.
  4. Use descriptive describe and it blocks.
  5. Avoid testing implementation details.
  6. Include edge cases in your tests.

11. Conclusion

  • Unit testing validates each part of your Express.js app independently.
  • Focus on Controllers, Middleware, and Services.
  • Use Jest and Supertest for efficient testing.

✨ Your Express.js application now has comprehensive unit tests! 🚀


Author: Mohin Sheikh

Follow me on GitHub for more insights!

Top comments (0)