将同一提供程序的两个实例嵌套JS [英] Nest JS two instances of the same provider
问题描述
在一个测试套件中,我似乎有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.
我将尝试分享一些片段,但是,当然,只有一个小仓库可以实际复制它,但是也许有人对此有见识:
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:
在单元测试中,您想要测试与其他依赖项(例如,依赖项)隔离的单个提供程序/控制器. UserService
或UsersController
.您将此提供程序及其注入的依赖项作为模拟导入.您不导入模块.
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.
假设我们有一个UsersService
依赖于DatabaseConnection
:
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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!