Testing Express Request Handlers with Jest
This challenge focuses on writing robust Jest tests for Express.js request handlers written in TypeScript. Testing request handlers is crucial for ensuring your API endpoints behave as expected, handling various inputs and edge cases gracefully.
Problem Description
You are provided with a set of Express.js request handlers written in TypeScript. Your task is to write Jest tests to verify the functionality of these handlers. The tests should cover various scenarios, including successful requests, error handling, and validation of request parameters. You'll need to mock the Express request and response objects to isolate the handlers for testing.
What needs to be achieved:
- Create a Jest testing environment that simulates Express.js requests.
- Write tests that assert the correct status codes, response bodies, and headers are returned by the request handlers.
- Test error handling logic within the handlers.
- Verify that request parameters are validated correctly.
Key Requirements:
- Use Jest and TypeScript.
- Mock the
reqandresobjects from Express.js. You can use libraries likesupertestor manually mock them. - Ensure your tests are isolated and do not rely on external dependencies (e.g., databases).
- Write clear and descriptive test names.
Expected Behavior:
The tests should pass if the request handlers function as described in the provided code and comments. Failures should indicate a discrepancy between the expected and actual behavior.
Edge Cases to Consider:
- Missing or invalid request parameters.
- Unexpected input data types.
- Error conditions within the handler logic (e.g., file not found, database connection error).
- Different HTTP methods (GET, POST, PUT, DELETE, etc.).
Examples
Example 1:
Let's say you have a handler that retrieves a user by ID:
// userController.ts
import { Request, Response } from 'express';
export const getUser = (req: Request, res: Response) => {
const userId = parseInt(req.params.id, 10);
if (isNaN(userId)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
// Simulate fetching user from database
const user = { id: userId, name: `User ${userId}` };
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
};
// userController.test.ts
const { getUser } = require('./userController');
const { jest } = require('@jest/globals');
describe('getUser', () => {
it('should return a user when the ID is valid', () => {
const req = { params: { id: '123' } };
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
getUser(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({ id: 123, name: 'User 123' });
});
it('should return 400 if the ID is not a number', () => {
const req = { params: { id: 'abc' } };
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
getUser(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({ error: 'Invalid user ID' });
});
it('should return 404 if the user is not found', () => {
const req = { params: { id: '456' } };
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
// Mock the user lookup to return null
jest.spyOn(global, 'user').mockReturnValue(null);
getUser(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({ error: 'User not found' });
// Restore the original user lookup
global.user.mockRestore();
});
});
Explanation: This example demonstrates mocking the request and response objects and asserting the expected status code and response body. It also shows how to mock a function call within the handler to simulate a specific scenario (user not found).
Example 2:
Consider a POST handler that creates a new resource:
// resourceController.ts
import { Request, Response } from 'express';
export const createResource = (req: Request, res: Response) => {
const { name } = req.body;
if (!name) {
return res.status(400).json({ error: 'Name is required' });
}
// Simulate creating resource in database
const newResource = { id: Math.random(), name };
res.status(201).json(newResource);
};
The tests would mock req.body and assert that a 201 status code is returned with the created resource in the response.
Constraints
- Test Coverage: Aim for at least 80% line coverage of the request handlers.
- Input Format: Request bodies should be JSON.
- Performance: Tests should execute within a reasonable timeframe (e.g., less than 1 second per test).
- Dependencies: Minimize external dependencies in your tests. Focus on mocking Express.js objects.
Notes
- Use
jest.mock()to mock modules if necessary. - Consider using
supertestfor more realistic request simulations, especially if you need to test middleware. However, manual mocking is perfectly acceptable for this challenge. - Pay close attention to the order of assertions. Ensure you call
res.status()beforeres.json(). - Think about different error scenarios and how your handlers should respond.
- Remember to restore any mocks you create to avoid affecting other tests.
mockRestore()is your friend.