如何开玩笑.仅基类方法,而不是覆盖方法



试图为我的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上进行一些单位测试的建议:

  1. 使用jest.mock((来简化模型(在这种情况下为Cacheservice(。请参阅https://jestjs.io/docs/en/es6-class-mocks#automatic-mock。

  2. 进行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);
  1. 如果您正确嘲笑一类,则不需要监视该方法(如果您不想模拟其实现(。

  2. 使用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 {
...
}

关于嘲笑另一个软件包的特定项目(而不是嘲笑整个软件包(,您可以这样做:

  1. 在您的规格文件中创建类(或者您可以在导入的另一个文件,甚至在共享模块中创建它(,该文件具有不同的名称但具有相同的公共方法名称。请注意,我们使用jest.fn((,因为我们不需要提供实现,并且已经在该方法中进行了监视(除非您必须嘲笑实现(。
class CacheServiceMock {
  setValue = jest.fn();
}
  1. 在设置测试模块的提供商时,请告诉您您正在"提供"原始类,但实际上提供了模拟的类:
const module = await Test.createTestingModule({
  controllers: [MyController],
  providers: [
    MyService,
    { provide: CacheService, useClass: CacheServiceMock },
  ],
}).compile();

有关提供商的更多信息,请参见https://angular.io/guide/depperity-indoction-providers(Nest遵循相同的Angular概念(。

最新更新