Building Resilient and Correct Systems
From "Does it work?" to "Is it correct?"
Testing is not about finding bugs. It is a discipline for building confidence.
Automated tests are your safety net.
This is the architectural blueprint for a healthy testing strategy. It describes the proportion of tests you should have at different levels.
Testing the Bricks in Isolation
A test that verifies the behavior of the smallest possible piece of your application (a "unit") in complete isolation.
For us, a unit is typically a single method within a Service.
To achieve isolation, we must replace real dependencies (like a database repository) with fakes. This is called mocking.
NestJS comes pre-configured with Jest, a popular and powerful testing framework.
Core Jest Functions:
A good unit test always follows this structure:
it('should add two numbers correctly', () => {
// 1. Arrange: Set up the test. Define inputs.
const num1 = 5;
const num2 = 10;
// 2. Act: Execute the code you are testing.
const result = calculator.add(num1, num2);
// 3. Assert: Check if the outcome is what you expect.
expect(result).toBe(15);
});
To test a service in isolation, we must mock its dependencies (like a TypeORM repository).
We create a mock object that has the same methods as the real repository, but they return fake, predictable data.
// A mock repository object for our tests
const mockRepository = {
find: jest.fn().mockResolvedValue([{ id: 1, name: 'Test User' }]),
findOneBy: jest.fn(),
create: jest.fn(dto => ({ id: Date.now(), ...dto })),
save: jest.fn(user => Promise.resolve(user)),
};
`users.service.spec.ts`
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User), // Use the real injection token
useValue: mockRepository, // But provide our mock object as the value
},
],
}).compile();
service = module.get(UsersService);
});
it('should find all users', async () => {
const users = await service.findAll();
expect(users).toEqual([{ id: 1, name: 'Test User' }]);
expect(mockRepository.find).toHaveBeenCalled();
});
});
Testing How the Bricks Work Together
A test that verifies the interaction between multiple components of your application.
Instead of mocking the database, an integration test for our API would typically involve:
The goal is to test a "slice" of your application.
This test uses a real test database connection.
describe('UsersService (Integration)', () => {
let service: UsersService;
let connection: DataSource;
beforeAll(async () => {
// Connect to the REAL test database
const module = await Test.createTestingModule({
imports: [AppModule], // Import the full app module
}).compile();
service = module.get(UsersService);
connection = module.get(DataSource);
});
beforeEach(async () => {
// Clean the users table before each test
await connection.getRepository(User).clear();
});
it('should create a user and find them', async () => {
const userDto = { name: 'Integration Test', email: 'test@int.com' };
const createdUser = await service.create(userDto);
const foundUser = await service.findOne(createdUser.id);
expect(foundUser.name).toBe(userDto.name);
});
});
Testing the Entire Skyscraper
An E2E test treats your application as a black box. It tests the entire system from the outside, exactly as a real user or client application would.
For a REST API, this means:
NestJS uses Jest as the test runner, but it uses a library called Supertest to make the HTTP requests.
Supertest provides a clean, chainable API for sending requests and making assertions against the response.
import * as request from 'supertest';
// ... inside an E2E test ...
it('/POST users', () => {
return request(app.getHttpServer()) // `app` is our running Nest app
.post('/users')
.send({ name: 'E2E Test', email: 'e2e@test.com' })
.expect(201) // Assert the status code
.expect(res => {
// Assert the response body
expect(res.body.name).toEqual('E2E Test');
});
});
The NestJS CLI generates a boilerplate E2E test file (`.e2e-spec.ts`) that handles the setup for you.
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('/GET', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
You will be given a `BooksService` that has methods for `create`, `findAll`, and `findOne`. Your task is to write the complete unit test file for this service, achieving 100% test coverage.