In the development of microservices with Express.js, ensuring the reliability and stability of your API endpoints is crucial. Mocked integration tests provide a way to verify the interactions between components without relying on actual external services or databases. This tutorial guides you through writing effective mocked integration tests for your Express microservices.

Understanding Mocked Integration Tests

Mocked integration tests simulate parts of your application environment, allowing you to test the interactions between your Express server and its dependencies in a controlled setting. Unlike unit tests, which isolate individual functions, mocked integration tests evaluate how different components work together, with dependencies replaced by mocks or stubs.

Setting Up the Testing Environment

To get started, you'll need a testing framework like Jest and a mocking library such as Supertest. Install them via npm:

  • npm install --save-dev jest supertest

Configure your package.json to include a test script:

"test": "jest"

Creating a Sample Express Service

Suppose you have a simple Express app that fetches user data from a database. Here's a basic example:

const express = require('express');
const app = express();

app.get('/users/:id', async (req, res) => {
  const userId = req.params.id;
  const user = await getUserFromDatabase(userId);
  if (user) {
    res.json(user);
  } else {
    res.status(404).send('User not found');
  }
});

module.exports = app;

async function getUserFromDatabase(id) {
  // Simulate database call
  return { id, name: 'John Doe' };
}

Writing Mocked Integration Tests

To test this service, you'll mock getUserFromDatabase to control its output during tests. Here's how to do it with Jest and Supertest:

const request = require('supertest');
const app = require('./app');

jest.mock('./app', () => {
  const originalModule = jest.requireActual('./app');
  return {
    __esModule: true,
    ...originalModule,
    getUserFromDatabase: jest.fn(),
  };
});

const { getUserFromDatabase } = require('./app');

describe('GET /users/:id', () => {
  it('should return user data when user exists', async () => {
    getUserFromDatabase.mockResolvedValue({ id: '1', name: 'Alice' });
    const response = await request(app).get('/users/1');
    expect(response.status).toBe(200);
    expect(response.body).toEqual({ id: '1', name: 'Alice' });
  });

  it('should return 404 when user does not exist', async () => {
    getUserFromDatabase.mockResolvedValue(null);
    const response = await request(app).get('/users/999');
    expect(response.status).toBe(404);
    expect(response.text).toBe('User not found');
  });
});

Best Practices for Mocked Integration Tests

When writing mocked integration tests, consider the following best practices:

  • Mock only the dependencies that are external or complex.
  • Ensure your mocks return realistic data to simulate real scenarios.
  • Test both successful and failure cases.
  • Keep tests isolated and idempotent.
  • Use descriptive test names to clarify behavior.

Conclusion

Mocked integration tests are a powerful tool for verifying the interactions within your Express microservices. By controlling dependencies through mocks, you can ensure your API behaves correctly under various conditions without relying on external systems. Incorporate these testing strategies into your development workflow to improve code quality and confidence.