试图为我的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的模拟模拟模拟了MyService上的" Findall",因此测试失败了,因为Spy_cacheset从未被调用。
我想做的是模拟唯一的 dbservice中的基本方法" findall",以便我维护MyService中存在的额外功能。
是否有一种方法可以这样做,而不仅仅是重命名MyService中的方法,我宁愿避免这样做?
编辑要添加:感谢@Jonatan Lenco的如此全面的回复,我已经在船上实施了。我还有一个问题。CacheService,Dbservice和许多其他内容(我想模拟的一些东西,其他我不想嘲笑(都在外部图书馆项目中,"共享"。
cache.service.ts
export class CacheService {...}
index.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_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 Decorator:
import { Controller, Get } from '@nestjs/common';
...
@Controller()
export class MyController {
...
}
关于嘲笑另一个软件包的特定项目(而不是嘲笑整个软件包(,您可以这样做:
- 在您的规格文件中创建类(或者您可以在导入的另一个文件,甚至在共享模块中创建它(,该文件具有不同的名称但具有相同的公共方法名称。请注意,我们使用jest.fn((,因为我们不需要提供实现,并且已经在该方法中进行了监视(除非您必须嘲笑实现(。
class CacheServiceMock {
setValue = jest.fn();
}
- 在设置测试模块的提供商时,请告诉您您正在"提供"原始类,但实际上提供了模拟的类:
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
{ provide: CacheService, useClass: CacheServiceMock },
],
}).compile();
有关提供商的更多信息,请参见https://angular.io/guide/depperity-indoction-providers(Nest遵循相同的Angular概念(。