我正在测试以下服务:
@Injectable()
export class TripService {
private readonly logger = new Logger('TripService');
constructor(
@InjectRepository(TripEntity)
private tripRepository: Repository<TripEntity>,
) { }
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;
}
}
我的存储库模拟:
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(),
})),
}));
我的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);
});
});
});
我想模拟对tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();
的呼叫
第一个问题,我应该在这里模拟链接的调用,因为我认为应该已经在Typeorm中对其进行了测试。
第二,如果我想模拟传递给每个链接调用的参数,最后还要模拟返回值,我该怎么办?
答案 0 :(得分:6)
我有类似的需求,并使用以下方法解决了
这是我尝试测试的代码。注意createQueryBuilder
和我调用的所有嵌套方法。
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;
现在,看看我编写的模拟上述方法的链接调用的测试。
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,
);
});
答案 1 :(得分:1)
Guilherme 的回答完全正确。我只是想提供一种可能适用于更多测试用例和 TypeScript 的修改方法。您可以使用 ()
,而不是将您的链式调用定义为 jest.fn
,从而允许您进行更多断言。例如,
/* 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`)