如何仅对基类方法进行jest.spy处理,而对覆盖方法不进行处理

时间:2019-05-24 10:57:58

标签: node.js jestjs nestjs

尝试为我的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。

我想做的只是模拟 only 来自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";

我是否仍可以仅将CacheService用作jest.mock(),而不模拟整个“共享”项目?

1 个答案:

答案 0 :(得分:2)

在这种情况下,由于您要监视抽象类(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的Testing实用程序,当您进行复杂的依赖项注入时,它将特别为您提供很多帮助。参见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 {

...

}