本文介绍了嵌套 JS 同一个提供者的两个实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您好,在测试套件中,在我看来,我有 2 个相同提供程序的活动实例,一个用于实现,另一个用于实际实现.

Hi on a test suite it appears to me that I have 2 living instances of a same provider, one for the implementation and another one for the real implementation.

我的结论基于这样一个事实,即在我的测试中,我尝试用 jest.fn 调用替换一个方法,但是,在我正在测试的服务上,该方法仍然指向原始实现.

I base my conclusion in a fact that on my test I tried replace a method by a jest.fn call but still, on the service I am testing the method still points to the original implementation.

更奇怪的是,我能够模拟另一个执行完全相同过程的服务,好像取决于这些服务是如何注入的(它们在容器图中来自何处)它会或不工作.

What makes it even more odd is that I was able to mock another service performing exactly the same procedure, as if, depending on how those services were injected (where they came from in the container graph) it would or not work.

我会尝试分享一些片段,但当然,只有一个小的 repo 才能真正重现它,但也许有人有见解:

I'll try to share some snippets, but of course, only a small repo could actually reproduce it, but perhaps someone has an insight:

 beforeAll(async done => {
    app = await Test.createTestingModule({
      imports: [
        SOME_MODULES,
      ],
      providers: [
        EssayApplicationService,
        ReviewFacade,
        ExamCacheResultService,
      ],
    }).compile();

    essayApplicationService = app.get<EssayApplicationService>(EssayApplicationService)
    reviewFacade = app.get<ReviewFacade>(ReviewFacade)
    examCacheResult = app.get<ExamCacheResultService>(ExamCacheResultService)
    await app.init()
    done()
  })
it('should invoke review only once', async done => {

    reviewFacade.startReview = jest.fn() --> this works
    examCacheResult.clearCachedResult = jest.fn() --> this fails

    await essayApplicationService.finishApplication()

    expect(reviewFacade.startReview).toHaveBeenCalledTimes(1)
    expect(reviewFacade.startReview).toHaveBeenCalledWith(expect.objectContaining({ id: 1 }))
    expect(examCacheResult.clearCachedResult).toHaveBeenCalledTimes(1) ---> here this fails, although it's called!!

所以,问题归结为这样一个事实,即我 100% 肯定这两种方法都在被测服务上被调用,但由于某种原因,第二个方法没有被模拟替换

So,the issue boils down to the fact that I'm 100% positive that both methods were called on the service under test, but the second for some reason wasn't replaced by the mock

推荐答案

您正在混合单元测试和端到端 (e2e) 测试的概念.您正在导入一个模块,同时直接导入单个提供程序.我假设您导入的模块之一也导入了 ExamCacheResultService.这样,您的测试应用程序中有两个.当您调用 app.get(ExamCacheResultService) 时,您将获得在您的测试模块中直接声明的实例.但是当您调用 finishApplication 时使用的是另一个.确定您要测试的内容并遵循以下原则:

You are mixing the concepts of unit tests and end to end (e2e) tests. You are importing a module and at the same time you import single providers directly. I'm assuming that one of your imported modules also imports ExamCacheResultService. This way, you have two of them in your test application. When you call app.get(ExamCacheResultService), you will get the instance that is directly declared in your testing module. But the one that is used when you call finishApplication is the other one. Decide what you want to test and follow the following principles:

在单元测试中,您希望测试与其他依赖项隔离的单个提供者/控制器,例如UserServiceUsersController.您将此提供程序及其注入的依赖项作为模拟导入.您没有导入模块.

In a unit test you want to test a single provider/controller isolated from other dependencies, e.g. UserService or UsersController. You import this provider and its injected dependencies as mocks. You do not import a module.

假设我们有一个依赖于 DatabaseConnectionUsersService:

Let's assume we have a UsersService that depends on a DatabaseConnection:

export class UsersService {
  constructor(private connection: DatabaseConnection) {}
  // ...
}

在你的单元测试中,你导入了 UsersService,你模拟了 DatabaseConnection 但你没有导入了 UsersModule代码>.

In your unit test, you import the UsersService, you mock the DatabaseConnection but you do not import the UsersModule.

module = await Test.createTestingModule({
  providers: [
    UsersService,
    { provide: DatabaseConnection, useClass: DbConnectionMock },
  ],
}).compile();
databaseMock = module.get(DatabaseConnection);
databaseMock.findMany.mockReturnValue([]);

端到端测试

在端到端测试中,您希望测试整个应用程序以及预先进行单元测试的部分之间的交互.因此,您不会导入单个提供程序,而是导入一个模块,通常是 AppModule.然后您可以覆盖单个提供者,例如如果您想在内存数据库而不是实际数据库上进行测试,或者您想模拟外部 API 的结果.

E2E test

In an end to end test, you want to test your whole application and therewith the interaction between the parts that you have unit tested beforehand. So you do not import single providers but instead a module, typically the AppModule. You can then override single providers, e.g. if you want to test on an in-memory database instead of an actual one or you want to mock the results of an external API.

const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    }).overrideProvider(DatabaseConnection).useClass(InMemoryDatabaseConnection)
      .overrideProvider(ExternalApiService).useValue(externalApiMock)
      .compile();
    app = moduleFixture.createNestApplication();
    externalApiMock.get.mockReturnValueOnce({data: [...]});
    await app.init();

如何创建模拟?

请参阅此答案.

这篇关于嵌套 JS 同一个提供者的两个实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-26 06:34