Testing Middleware
⭐ Interview Importance: HIGH
⏱️ Revision Time: 6 min
Testing Middleware involves simulating the Express Request, Response, and NextFunction objects to verify that the middleware correctly mutates the request or terminates it before it reaches the controller.
Overview
Middleware in NestJS operates exactly like Express middleware. It is a function (or a class implementing NestMiddleware) that receives (req, res, next).
Because middleware sits at the very edge of the application (before Guards, Interceptors, and Pipes), unit testing it requires manually mocking the raw HTTP objects.
Key Concepts
reqandresMocking: You must provide partial implementations of the Express Request and Response objects.next()Function: Thenextfunction is just a callback. Your test must verify whethernext()was called (meaning the request is allowed to proceed) or if it was not called (meaning the middleware intercepted and ended the request).
Code Examples
1. Testing a Class-Based Middleware
Imagine a middleware that checks for a specific custom header and attaches a user ID to the request. If the header is missing, it returns a 401 Unauthorized immediately, terminating the request.
// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['x-custom-token'];
if (!token) {
throw new UnauthorizedException('Token is missing');
}
if (token === 'valid-token') {
req['userId'] = 123;
next();
} else {
throw new UnauthorizedException('Invalid token');
}
}
}
Now, the Unit Test:
// auth.middleware.spec.ts
import { AuthMiddleware } from './auth.middleware';
import { UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
describe('AuthMiddleware', () => {
let middleware: AuthMiddleware;
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let nextFunction: NextFunction;
beforeEach(() => {
middleware = new AuthMiddleware();
// We only mock the properties of the Request that the middleware actually uses
mockRequest = {
headers: {},
};
mockResponse = {}; // Empty mock, as this middleware doesn't modify the response directly
// next is just a Jest mock function
nextFunction = jest.fn();
});
it('should call next() and attach userId if token is valid', () => {
// Arrange
mockRequest.headers = { 'x-custom-token': 'valid-token' };
// Act
middleware.use(mockRequest as Request, mockResponse as Response, nextFunction);
// Assert
expect(mockRequest['userId']).toBe(123);
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it('should throw UnauthorizedException if token is missing', () => {
// Arrange
mockRequest.headers = {};
// Act & Assert
expect(() => {
middleware.use(mockRequest as Request, mockResponse as Response, nextFunction);
}).toThrow(UnauthorizedException);
// next() should NEVER be called if an exception is thrown
expect(nextFunction).not.toHaveBeenCalled();
});
});
Best Practices
- Use
Partial<T>: Always use TypeScript’s built-inPartial<Request>when mocking standard Express/Fastify objects. Creating a full mock of the ExpressRequestobject is nearly impossible because it has dozens of complex methods and properties.Partialallows you to only stub what you need. - Testing Dependency Injection in Middleware: If your middleware requires dependencies (e.g.,
constructor(private db: DbService)), do not usenew AuthMiddleware(). Instead, useTest.createTestingModule()to compile the module, provide a mock forDbService, and then retrieve the middleware usingmodule.get(AuthMiddleware).