CodePark/ questions / Unit testing services
START PRACTICINGPRACTICE
Backend question · Backend testing › Unit testing services

Write down the unit tests you would want for rename() below, using a mocked repository. Which cases do you cover and what exactly do you assert?

@Injectable()
export class UsersService {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}

  async rename(id: number, name: string) {
    const user = await this.repo.findOneBy({ id });
    if (!user) throw new NotFoundException();
    user.name = name.trim();
    return this.repo.save(user);
  }
}

Test two behaviours: the missing-user case and the happy path. Assert results, not every call.

Setup. Create repo = { findOneBy: jest.fn(), save: jest.fn() }. Provide it in Test.createTestingModule with { provide: getRepositoryToken(User), useValue: repo }. Or just call new UsersService(repo as any), because there is nothing else to wire.

Missing user:

  • Make findOneBy resolve to null.
  • Assert await expect(service.rename(1, 'x')).rejects.toThrow(NotFoundException).
  • Check that save was not called.

Happy path:

  • Make findOneBy resolve to a user.
  • Make save return its argument with mockImplementation(async (u) => u).
  • Assert that the result has the trimmed name.
  • Assert that save received that user.

Don't check exactly how findOneBy was called, unless that is the behaviour you care about. Whether the query really finds the row belongs in an integration test.

  • Provide the mock with getRepositoryToken(User), or create the class directly.
  • For the not-found path, await rejects.toThrow(NotFoundException) and check that save was not called.
  • For the happy path, check that the returned value has the trimmed name and that save received it.
  • Don't over-specify calls, because only an integration test can prove the query is correct.