如何 jest.spyOn 只有基类方法,而不是重写的方法

埃里克

尝试为我的 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 单元测试的建议:

  1. 使用 jest.mock() 以简化您的模拟(在本例中为 CacheService)。请参阅https://jestjs.io/docs/en/es6-class-mocks#automatic-mock

  2. 当您执行 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);
  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 装饰器:

import { Controller, Get } from '@nestjs/common';

...

@Controller()
export class MyController {

...

}

关于模拟另一个包的特定项目(而不是模拟整个包),您可以这样做:

  1. 在您的规范文件中创建一个类(或者您可以在您导入的另一个文件中创建它,甚至在您的共享模块中创建它),该类具有不同的名称但具有相同的公共方法名称。请注意,我们使用 jest.fn() 是因为我们不需要提供实现,并且已经在方法中进行了监视(以后无需执行 jest.spyOn(),除非您必须模拟实现)。
class CacheServiceMock {
  setValue = jest.fn();
}
  1. 在设置测试模块的提供者时,告诉它您正在“提供”原始类,但实际上提供的是模拟类:
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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章