Unit tests
A unit test checks one class apart from the rest of the application. The posts service needs a repository to work, but a test does not need a real database: the repository is replaced with a mock — an object with the same methods that return predefined data. A NestJS project is already set up with Jest, a library for running tests.
Where tests live
A test sits next to its class and has the same name ending in .spec.ts: for src/posts/posts.service.ts it is src/posts/posts.service.spec.ts. nest g service creates this file for you.
npm run testThe command finds every *.spec.ts file in the src folder and runs it.
The testing module
Test.createTestingModule() builds a small module from just the providers the test needs:
// src/posts/posts.service.spec.ts
import { NotFoundException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Post } from './entities/post.entity';
import { PostsService } from './posts.service';
describe('PostsService', () => {
let service: PostsService;
const repository = {
findOne: jest.fn(),
find: jest.fn(),
};
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
PostsService,
{ provide: getRepositoryToken(Post), useValue: repository },
],
}).compile();
service = moduleRef.get(PostsService);
jest.clearAllMocks();
});
it('returns a post by id', async () => {
repository.findOne.mockResolvedValue({ id: 1, title: 'First post' });
const post = await service.findOne(1);
expect(post.title).toBe('First post');
});
it('throws 404 when the post does not exist', async () => {
repository.findOne.mockResolvedValue(null);
await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});
});getRepositoryToken(Post) is the key under which NestJS keeps the posts repository. The { provide, useValue } entry says: "instead of the real repository, hand out this object". jest.fn() creates a mock function, and mockResolvedValue() sets the promise it returns.
The result
PASS src/posts/posts.service.spec.ts
PostsService
√ returns a post by id (5 ms)
√ throws 404 when the post does not exist (3 ms)
Tests: 2 passed, 2 totalIf you break findOne() — for example, remove the null check — the second test shows FAIL and explains that an exception was expected.
Checking calls
A mock remembers how it was called:
// src/posts/posts.service.spec.ts
it('loads the post together with comments', async () => {
repository.findOne.mockResolvedValue({ id: 1 });
await service.findOne(1);
expect(repository.findOne).toHaveBeenCalledWith({
where: { id: 1 },
relations: { comments: true },
});
});Useful commands
| Command | What it does |
|---|---|
npm run test | Runs all unit tests once |
npm run test:watch | Reruns tests when files change |
npm run test:cov | Shows how much of the code is covered by tests |
Common mistake
A test fails with Nest can't resolve dependencies of the PostsService (?). The testing module knows nothing about TypeOrmModule — every dependency of the service must be passed to providers yourself, real or mocked. The test generated by the CLI contains only the service, so after adding a repository you must extend it.