尝试为我的 nestjs 应用程序编写测试脚本。
我有控制器/服务框架,看起来像这样:
控制器:
export class MyController {
constructor(
protected _svc: MyService
) {}
@Get()
async getAll(): Promise<Array<Person>> {
return await this._svc.findAll();
}
}
服务:
@Injectable()
export class MyService extends DbService < Person > {
constructor(
private _cache: CacheService
) {
super(...);
}
async findAll() {
return super.findAll().then(res => {
res.map(s => {
this._cache.setValue(`key${s.ref}`, s);
});
return res;
});
}
基类:
@Injectable()
export abstract class DbService<T> {
constructor() {}
async findAll(): Promise<Array<T>> {
...
}
}
我的控制器是在 API 上调用端点时的入口点。这调用了服务,它扩展了 DbService,它与我的数据库进行通信。有很多服务都扩展了这个 DbService。在这种情况下,MyService 类会覆盖 DbService“findAll”方法来执行一些缓存操作。
我的测试脚本有这个:
let myController: MyController;
let myService: MyService;
describe("MyController", async () => {
let spy_findall, spy_cacheset;
beforeAll(() => {
this._cacheService = {
// getValue, setValue, delete methods
};
myService = new MyService(this._cacheService);
myController = new MyController(myService);
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
spy_cacheset = jest.spyOn(this._cacheService, "setValue");
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe("getAll", () => {
it("should return an array of one person", async () => {
await myController.getAll().then(r => {
expect(r).toHaveLength(1);
expect(spy_findall).toBeCalledTimes(1);
expect(spy_cacheset).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
});
现在,很明显 findAll 的 mockImplementation 模拟 MyService 上的“findAll”,因此测试失败,因为 spy_cacheset 从未被调用。
我想要做的是仅模拟DbService 中的基本方法“findAll”,以便维护 MyService 中存在的额外功能。
有没有办法在不重命名 MyService 中的方法的情况下做到这一点,我宁愿避免这样做?
编辑添加:感谢@Jonatan lenco 提供如此全面的回复,我已接受并实施了该回复。我还有一个问题。CacheService、DbService 和很多其他东西(其中一些我想模拟,其他我不想)都在一个外部库项目中,“共享”。
缓存服务.ts
export class CacheService {...}
索引.ts
export * from "./shared/cache.service"
export * from "./shared/db.service"
export * from "./shared/other.stuff"
....
然后将其编译并作为一个包包含在 node_modules 中。
在我编写测试的项目中:
import { CacheService, DocumentService, OtherStuff } from "shared";
我是否仍然可以将 jest.mock() 仅用于 CacheService,而不模拟整个“共享”项目?
在这种情况下,由于要监视抽象类(DbService),因此可以监视原型方法:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
这里还有一些关于 NestJS 和 Jest 单元测试的建议:
使用 jest.mock() 以简化您的模拟(在本例中为 CacheService)。请参阅https://jestjs.io/docs/en/es6-class-mocks#automatic-mock。
当您执行 jest.spyOn() 时,您可以在不需要 spy 对象的情况下断言方法执行。代替:
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
...
expect(spy_findall).toBeCalledTimes(1);
你可以做:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
...
expect(DbService.prototype.findAll).toBeCalledTimes(1);
如果您正在正确模拟一个类,则不需要监视该方法(如果您不想模拟其实现)。
使用 NestJS 的测试实用程序,当您有复杂的依赖注入时,它会特别有帮助。请参阅https://docs.nestjs.com/fundamentals/testing#testing-utilities。
以下是将这 4 条建议应用于单元测试的示例:
import { Test } from '@nestjs/testing';
import { CacheService } from './cache.service';
import { DbService } from './db.service';
import { MyController } from './my.controller';
import { MyService } from './my.service';
import { Person } from './person';
jest.mock('./cache.service');
describe('MyController', async () => {
let myController: MyController;
let myService: MyService;
let cacheService: CacheService;
const testPerson = new Person();
beforeAll(async () => {
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
CacheService,
],
}).compile();
myService = module.get<MyService>(MyService);
cacheService = module.get<CacheService>(CacheService);
myController = module.get<MyController>(MyController);
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe('getAll', () => {
it('Should return an array of one person', async () => {
const r = await myController.getAll();
expect(r).toHaveLength(1);
expect(DbService.prototype.findAll).toBeCalledTimes(1);
expect(cacheService.setValue).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
注意:为了让测试实用程序正常工作,并且让您的应用程序正常工作,您需要在 MyController 类上添加 @Controller 装饰器:
import { Controller, Get } from '@nestjs/common';
...
@Controller()
export class MyController {
...
}
关于模拟另一个包的特定项目(而不是模拟整个包),您可以这样做:
class CacheServiceMock {
setValue = jest.fn();
}
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
{ provide: CacheService, useClass: CacheServiceMock },
],
}).compile();
有关提供程序的更多信息,请参阅https://angular.io/guide/dependency-injection-providers(Nest遵循与 Angular 相同的想法)。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句