Testing Services
It is possible to test services that use TypeOrmQueryService
. The process is similar to the one described for nestjs, but it has a few differences.
Let's assume we have the following TodoItem
service. For the sake of completeness, let's also add a dependency on another service (let's pretend that the todos have subTasks; we are not using relationships here):
todo-item.service.ts
import { InjectQueryService, QueryService } from '@nestjs-query/core';import { TypeOrmQueryService } from '@nestjs-query/query-typeorm';import { Repository } from 'typeorm';import { InjectRepository } from '@nestjs/typeorm';import { SubTaskEntity } from '../sub-task/sub-task.entity';import { TodoItemEntity } from './todo-item.entity';
@QueryService(TodoItemEntity)export class TodoItemService extends TypeOrmQueryService<TodoItemEntity> { constructor( @InjectRepository(TodoItemEntity) private todosRepository: Repository<TodoItemEntity>, @InjectQueryService(SubTaskEntity) private subTaskService: QueryService<SubTaskEntity>, ) { super(todosRepository); }
async getWithSubTasks(id: number): Promise<{ todoItem: TodoItemEntity; subTasks: SubTaskEntity[] }> { const todoItem = await this.todosRepository.findOneOrFail(id); const subTasks = await this.subTaskService.query({ filter: { todoItemId: { eq: id } } }); return { todoItem, subTasks }; }}
Now lets writ a some tests!
todo-item.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';import { getQueryServiceToken } from '@nestjs-query/core';import { getRepositoryToken } from '@nestjs/typeorm';import { plainToClass } from 'class-transformer';import { TodoItemEntity } from '../src/todo-item/todo-item.entity';import { SubTaskEntity } from '../src/sub-task/sub-task.entity';import { TodoItemService } from '../src/todo-item/todo-item.service';
// We create some fake entiites, just for testing. Here they are empty,// but they can be more complex, depending on the testing cases.const subTasks = [new SubTaskEntity(), new SubTaskEntity(), new SubTaskEntity()];const oneTodo: TodoItemEntity = plainToClass(TodoItemEntity, { id: 1, title: 'A test todo' });
describe('TodosItemService', () => { let service: TodoItemService; // Removed type, compared to the nestjs examples
// We mock the responses of the two services. // The mocks in this example are very simple, but they can be more complex, depending on the test cases.
const mockedSubTaskService = { // mock the query method that is used by getWithSubTasks query: jest.fn((query) => Promise.resolve(subTasks)), }; const mockedRepo = { // mock the repo `findOneOrFail` findOneOrFail: jest.fn((id) => Promise.resolve(oneTodo)), };
beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ // Provide the original service TodoItemService, // Mock the repository using the `getRepositoryToken` from @nestjs/typeorm { provide: getRepositoryToken(TodoItemEntity), useValue: mockedRepo, }, // Mock the SubTask QueryService using the `getQueryServiceToken` from @nestjs-query/core { provide: getQueryServiceToken(SubTaskEntity), useValue: mockedSubTaskService, }, ], }).compile(); // get the service from the testing module. service = await module.get(TodoItemService); });
// reset call counts and called with arguments after each spec afterEach(() => jest.clearAllMocks());
// Now we are ready to write the tests. describe('getWithSubTasks', () => { it('should return a TodoItem with subTasks', async () => { // We can use jest spies to inspect if functions are called ...
// create a spy for the repository findOneOrFail method const findOneOrFailSpy = jest.spyOn(mockedRepo, 'findOneOrFail'); // create a spy for the mocked subTaskService query method const querySpy = jest.spyOn(mockedSubTaskService, 'query');
// When we call a service function the following things happen: // - the real service function is called, so we can test its code // - the mocked repository method is called // - the mocked subTask query service method is called // note that if the service calls a function in a repo or query service that is not defined by a mock, the test // will fail const todo = await service.getWithSubTasks(oneTodo.id); // check the result against the expected results expect(todo).toEqual({ todoItem: oneTodo, subTasks });
// Ensure that the spies are called once with the appropriate arguments expect(findOneOrFailSpy).toHaveBeenCalledTimes(1); expect(findOneOrFailSpy).toHaveBeenCalledWith(oneTodo.id); expect(querySpy).toHaveBeenCalledTimes(1); expect(querySpy).toHaveBeenCalledWith({ filter: { todoItemId: { eq: oneTodo.id } } }); }); });});
#
Mocking Inherited MethodsYou can also mock inherited methods.
Let's change the getWithSubTasks
method from the TodoItemService
to use the getById
method from the parent TypeOrmQueryService
async getWithSubTasks(id: number): Promise<{ todoItem: TodoItemEntity; subTasks: SubTaskEntity[] }> { const todoItem = await this.getById(id); const subTasks = await this.subTaskService.query({ filter: { todoItemId: { eq: id } } }); return { todoItem, subTasks };}
To mock the getById
method we can create a new spy
with a mock implementation
const getByIdSpy = jest.spyOn(service, 'getById').mockImplementation(() => Promise.resolve(oneTodo));
Lets update our tests to mock out the getById
implementation
describe('getWithSubTasks', () => { it('should return a TodoItem with subTasks', async () => { // We can use jest spies to inspect if functions are called ...
// create a mock implementation for getById on the service const getByIdSpy = jest.spyOn(service, 'getById').mockImplementation(() => Promise.resolve(oneTodo)); // create a spy for the mocked subTaskService query method const querySpy = jest.spyOn(mockedSubTaskService, 'query');
// When we call a service function the following things happen: // - the real service function is called, so we can test its code // - the mock todoItem query service method is called // - the mocked subTask query service method is called // note that if the service calls a function in a repo or query service that is not defined by a mock, the test // will fail const todo = await service.getWithSubTasks(oneTodo.id); // check the result against the expected results expect(todo).toEqual({ todoItem: oneTodo, subTasks });
// Ensure that the spies are called once with the appropriate arguments expect(getByIdSpy).toHaveBeenCalledTimes(1); expect(getByIdSpy).toHaveBeenCalledWith(oneTodo.id); expect(querySpy).toHaveBeenCalledTimes(1); expect(querySpy).toHaveBeenCalledWith({ filter: { todoItemId: { eq: oneTodo.id } } }); });
it('should reject if the getById rejects with an error', async () => { // We can use jest spies to inspect if functions are called ...
// create a mock implementation for the service getById method const getByIdSpy = jest.spyOn(service, 'getById').mockImplementation(() => Promise.reject(new Error('foo'))); // create a spy for the mocked subTaskService query method const querySpy = jest.spyOn(mockedSubTaskService, 'query');
// When we call a service function the following things happen: // - the real service function is called, so we can test its code // - the mocked repository method is called // - the mocked subTask query service method is called // note that if the service calls a function in a repo or query service that is not defined by a mock, the test // will fail await expect(service.getWithSubTasks(oneTodo.id)).rejects.toThrow('foo');
// Ensure that the getById spy is called one expect(getByIdSpy).toHaveBeenCalledTimes(1); expect(getByIdSpy).toHaveBeenCalledWith(oneTodo.id); // Ensure that that the querySpy was not called expect(querySpy).not.toHaveBeenCalled(); });});