[英]Inject TypeORM repository into NestJS service for mock data testing
假設我們有一個非常簡單的服務,它通過 id 查找用戶實體:
export class UserService {
constructor(@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
}
async findUser(userId: string): Promise<UserEntity> {
return this.userRepository.findOne(userId);
}
}
然后您可以使用以下模擬工廠模擬UserRepository
(根據需要添加更多方法):
// @ts-ignore
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
// ...
}));
使用工廠可確保每次測試都使用新的模擬。
describe('UserService', () => {
let service: UserService;
let repositoryMock: MockType<Repository<UserEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
// Provide your mock instead of the actual repository
{ provide: getRepositoryToken(UserEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<UserService>(UserService);
repositoryMock = module.get(getRepositoryToken(UserEntity));
});
it('should find a user', async () => {
const user = {name: 'Alni', id: '123'};
// Now you can control the return value of your mock's methods
repositoryMock.findOne.mockReturnValue(user);
expect(service.findUser(user.id)).toEqual(user);
// And make assertions on how often and with what params your mock's methods are called
expect(repositoryMock.findOne).toHaveBeenCalledWith(user.id);
});
});
為了類型安全和舒適,您可以為您的(部分)模擬使用以下類型(遠非完美,當 jest 本身在即將到來的主要版本中開始使用 typescript 時可能會有更好的解決方案):
export type MockType<T> = {
[P in keyof T]?: jest.Mock<{}>;
};
我的解決方案使用 sqlite 內存數據庫,我在其中插入所有需要的數據並在每次測試運行之前創建模式。 因此,每個測試都使用相同的數據集,您不必模擬任何 TypeORM 方法:
import { Test, TestingModule } from "@nestjs/testing";
import { CompanyInfo } from '../../src/company-info/company-info.entity';
import { CompanyInfoService } from "../../src/company-info/company-info.service";
import { Repository, createConnection, getConnection, getRepository } from "typeorm";
import { getRepositoryToken } from "@nestjs/typeorm";
describe('CompanyInfoService', () => {
let service: CompanyInfoService;
let repository: Repository<CompanyInfo>;
let testingModule: TestingModule;
const testConnectionName = 'testConnection';
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
CompanyInfoService,
{
provide: getRepositoryToken(CompanyInfo),
useClass: Repository,
},
],
}).compile();
let connection = await createConnection({
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [CompanyInfo],
synchronize: true,
logging: false,
name: testConnectionName
});
repository = getRepository(CompanyInfo, testConnectionName);
service = new CompanyInfoService(repository);
return connection;
});
afterEach(async () => {
await getConnection(testConnectionName).close()
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return company info for findOne', async () => {
// prepare data, insert them to be tested
const companyInfoData: CompanyInfo = {
id: 1,
};
await repository.insert(companyInfoData);
// test data retrieval itself
expect(await service.findOne()).toEqual(companyInfoData);
});
});
我在這里得到了啟發: https ://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549
您還可以使用測試數據庫並在那里插入數據。
describe('EmployeesService', () => {
let employeesService: EmployeesService;
let moduleRef: TestingModule;
beforeEach(async () => {
moduleRef = await Test.createTestingModule({
imports: [
TypeOrmModule.forFeature([Employee]),
TypeOrmModule.forRoot({
type: 'postgres',
host: 'db',
port: 5432,
username: 'postgres',
password: '',
database: 'test',
autoLoadEntities: true,
synchronize: true,
}),
],
providers: [EmployeesService],
}).compile();
employeesService = moduleRef.get<EmployeesService>(EmployeesService);
});
afterEach(async () => {
// Free DB connection for next test
await moduleRef.close();
});
describe('findOne', () => {
it('returns empty array', async () => {
expect(await employeesService.findAll()).toStrictEqual([]);
});
});
});
您將需要手動創建數據庫,例如psql -U postgres -c 'create database test;'
. 架構同步將自動發生。
我還發現這對我有用:
export const mockRepository = jest.fn(() => ({
metadata: {
columns: [],
relations: [],
},
}));
和
const module: TestingModule = await Test.createTestingModule({
providers: [{ provide: getRepositoryToken(Entity), useClass: mockRepository }],
}).compile();
從上述想法開始並幫助模擬任何類,我們提出了這個 MockFactory:
export type MockType<T> = {
[P in keyof T]?: jest.Mock<unknown>;
};
export class MockFactory {
static getMock<T>(type: new (...args: any[]) => T, includes?: string[]): MockType<T> {
const mock: MockType<T> = {};
Object.getOwnPropertyNames(type.prototype)
.filter((key: string) => key !== 'constructor' && (!includes || includes.includes(key)))
.map((key: string) => {
mock[key] = jest.fn();
});
return mock;
}
}
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getRepositoryToken(MyCustomRepository),
useValue: MockFactory.getMock(MyCustomRepository)
}
]
}).compile();
首先,我是 Ts/Js/Node 的新手。 這是我的示例代碼:它允許您在測試期間將 NEST 的注入系統與自定義連接一起使用。 以這種方式,服務/控制器對象不是手動創建的,而是由 TestingModule 連接的:
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import {
Repository,
createConnection,
getConnection,
getRepository,
Connection,
} from 'typeorm';
import { Order } from './order';
import { OrdersService } from './orders.service';
describe('Test Orders', () => {
let repository: Repository<Order>;
let service: OrdersService;
let connection: Connection;
beforeEach(async () => {
connection = await createConnection({
type: 'sqlite',
database: './test.db',
dropSchema: true,
entities: [Order],
synchronize: true,
logging: true,
});
repository = getRepository(Order);
const testingModule = await Test.createTestingModule({
providers: [
OrdersService,
{
provide: getRepositoryToken(Order, connection),
useFactory: () => {
return repository;
},
},
],
}).compile();
console.log('Getting Service from NEST');
service = testingModule.get<OrdersService>(OrdersService);
return connection;
});
afterEach(async () => {
await getConnection().close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('CRUD Order Test', async () => {
const order = new Order();
order.currency = 'EURO';
order.unitPrice = 12.0;
order.issueDate = new Date();
const inserted = await service.create(order);
console.log('Inserted order ', inserted.id); // id is the @PrimaryGeneratedColumn() key
let allOrders = await service.findAll();
expect(allOrders.length).toBe(1);
await service.delete(inserted.id);
allOrders = await service.findAll();
expect(allOrders.length).toBe(0);
});
});
與上一個答案中定義的建議 MockTypes 類似的是 TypedMockType
type ArgsType<T> = T extends (...args: infer A) => unknown ? A : never; export type TypedMockType<T> = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [P in keyof T]: T[P] extends (...args: any) => unknown ? jest.Mock<ReturnType<T[P]>, ArgsType<T[P]>> : never; };
這是一種可以與 MockType 一樣使用的實用程序類型,但不同之處在於您的原始方法簽名的有效負載將是相同的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.