I am testing the following service:
@Injectable()
export class TripService {
private readonly logger = new Logger('TripService');
constructor(
@InjectRepository(TripEntity)
private tripRepository: Repository<TripEntity>
) {}
public async showTrip(clientId: string, tripId: string): Promise<Partial<TripEntity>> {
const trip = await this.tripRepository
.createQueryBuilder('trips')
.innerJoinAndSelect('trips.driver', 'driver', 'driver.clientId = :clientId', { clientId })
.where({ id: tripId })
.select([
'trips.id',
'trips.distance',
'trips.sourceAddress',
'trips.destinationAddress',
'trips.startTime',
'trips.endTime',
'trips.createdAt'
])
.getOne();
if (!trip) {
throw new HttpException('Trip not found', HttpStatus.NOT_FOUND);
}
return trip;
}
}
My repository mock:
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
findAndCount: jest.fn(entity => entity),
create: jest.fn(entity => entity),
save: jest.fn(entity => entity),
update: jest.fn(entity => entity),
delete: jest.fn(entity => entity),
createQueryBuilder: jest.fn(() => ({
delete: jest.fn().mockReturnThis(),
innerJoinAndSelect: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
execute: jest.fn().mockReturnThis(),
getOne: jest.fn().mockReturnThis(),
})),
}));
My tripService.spec.ts:
import { Test, TestingModule } from '@nestjs/testing';
import { TripService } from './trip.service';
import { MockType } from '../mock/mock.type';
import { Repository } from 'typeorm';
import { TripEntity } from './trip.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
import { repositoryMockFactory } from '../mock/repositoryMock.factory';
import { DriverEntity } from '../driver/driver.entity';
import { plainToClass } from 'class-transformer';
describe('TripService', () => {
let service: TripService;
let tripRepositoryMock: MockType<Repository<TripEntity>>;
let driverRepositoryMock: MockType<Repository<DriverEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TripService,
{ provide: getRepositoryToken(DriverEntity), useFactory: repositoryMockFactory },
{ provide: getRepositoryToken(TripEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<TripService>(TripService);
driverRepositoryMock = module.get(getRepositoryToken(DriverEntity));
tripRepositoryMock = module.get(getRepositoryToken(TripEntity));
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(driverRepositoryMock).toBeDefined();
expect(tripRepositoryMock).toBeDefined();
});
describe('TripService.showTrip()', () => {
const trip: TripEntity = plainToClass(TripEntity, {
id: 'one',
distance: 123,
sourceAddress: 'one',
destinationAddress: 'one',
startTime: 'one',
endTime: 'one',
createdAt: 'one',
});
it('should show the trip is it exists', async () => {
tripRepositoryMock.createQueryBuilder.mockReturnValue(trip);
await expect(service.showTrip('one', 'one')).resolves.toEqual(trip);
});
});
});
I want to mock the call to the tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();
First question, should I mock the chained calls here because I assume that it should already be tested in Typeorm.
Second, if I want to mock the parameters passed to each chained call and finally also mock the return value, how can I go about it?
You can use mockFn. mockReturnThis() to do this. const client = { items: () => { return client; }, type: (name: string) => { return client; }, toObservable: () => { return client; }, subscribe: handler => { handler(); return client; } }; export { client };
Function mock using jest. The simplest and most common way of creating a mock is jest. fn() method. If no implementation is provided, it will return the undefined value. There is plenty of helpful methods on returned Jest mock to control its input, output and implementation.
Let's refactor the test to use this approach: test("Should render character name", async () => { const mock = jest. spyOn(data, "getCharacter"). mockResolvedValue("Bob"); render(<Hello id={1} />); expect(await screen.
I had a similar need and solved using the following approach.
This is the code I was trying to test. Pay attention to the createQueryBuilder
and all the nested methods I called.
const reactions = await this.reactionEntity
.createQueryBuilder(TABLE_REACTIONS)
.select('reaction')
.addSelect('COUNT(1) as count')
.groupBy('content_id, source, reaction')
.where(`content_id = :contentId AND source = :source`, {
contentId,
source,
})
.getRawMany<GetContentReactionsResult>();
return reactions;
Now, take a look at the test I wrote that simulates the chained calls of the above methods.
it('should return the reactions that match the supplied parameters', async () => {
const PARAMS = { contentId: '1', source: 'anything' };
const FILTERED_REACTIONS = REACTIONS.filter(
r => r.contentId === PARAMS.contentId && r.source === PARAMS.source,
);
// Pay attention to this part. Here I created a createQueryBuilder
// const with all methods I call in the code above. Notice that I return
// the same `createQueryBuilder` in all the properties/methods it has
// except in the last one that is the one that return the data
// I want to check.
const createQueryBuilder: any = {
select: () => createQueryBuilder,
addSelect: () => createQueryBuilder,
groupBy: () => createQueryBuilder,
where: () => createQueryBuilder,
getRawMany: () => FILTERED_REACTIONS,
};
jest
.spyOn(reactionEntity, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
await expect(query.getContentReactions(PARAMS)).resolves.toEqual(
FILTERED_REACTIONS,
);
});
Guilherme's answer is totally right. I just wanted to offer a modified approach that might apply to more test cases, and in TypeScript. Instead of defining your chained calls as ()
, you can use a jest.fn
, allowing you to make more assertions. e.g.,
/* eslint-disable @typescript-eslint/no-explicit-any */
const createQueryBuilder: any = {
select: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
addSelect: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
groupBy: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
where: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
getRawMany: jest
.fn()
.mockImplementationOnce(() => {
return FILTERED_REACTIONS
})
.mockImplementationOnce(() => {
return SOMETHING_ELSE
}),
}
/* run your code */
// then you can include an assertion like this:
expect(createQueryBuilder.groupBy).toHaveBeenCalledWith(`some group`)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With