在测试外重写NestJS Service时实例化异常问题排查
NestJS:替换Provider却同时实例化原类与Mock类的问题
我编写了如下NestJS代码:
import { Module, Injectable } from '@nestjs/common' @Injectable() export class PrismaService { constructor() { console.log('init original') } } @Injectable() export class PrismaServiceMock { constructor() { console.log('init mock') } } @Module({ providers: [ { provide: PrismaService, useClass: PrismaService, }, ], exports: [PrismaService], }) export class PrismaModule {} @Module({ imports: [PrismaModule], providers: [ { provide: PrismaService, useClass: PrismaServiceMock, }, ], }) export class AppModule {}
我期望通过AppModule中的配置仅实例化PrismaServiceMock,但实际PrismaService和PrismaServiceMock都被创建了,请问问题出在哪?
问题原因
核心问题是PrismaModule内部已经明确声明并实例化了原PrismaService。当AppModule导入PrismaModule时,NestJS会先初始化导入模块(PrismaModule)的所有Providers,所以原PrismaService会被创建。而AppModule里的Provider配置只是在自身模块作用域内覆盖了PrismaService的实现,但无法回溯修改已导入模块的初始化逻辑。
解决方法
方法一:让PrismaModule支持动态配置
修改PrismaModule,通过静态方法接收配置选项,动态决定使用原服务还是Mock服务:
import { Module, Injectable } from '@nestjs/common' @Injectable() export class PrismaService { constructor() { console.log('init original') } } @Injectable() export class PrismaServiceMock { constructor() { console.log('init mock') } } @Module({}) export class PrismaModule { static forRoot(options: { useMock?: boolean } = {}) { const prismaProvider = options.useMock ? { provide: PrismaService, useClass: PrismaServiceMock } : { provide: PrismaService, useClass: PrismaService }; return { module: PrismaModule, providers: [prismaProvider], exports: [PrismaService], }; } } @Module({ imports: [PrismaModule.forRoot({ useMock: true })], }) export class AppModule {}
这样AppModule导入时通过forRoot传入useMock: true,PrismaModule就只会实例化Mock类,原类不会被创建。
方法二:测试环境下使用overrideProvider(仅适用于测试场景)
如果只是在单元/集成测试中需要替换服务,不需要修改模块代码,直接在测试用例中用overrideProvider覆盖:
import { Test } from '@nestjs/testing'; import { AppModule } from './app.module'; import { PrismaService } from './prisma.service'; import { PrismaServiceMock } from './prisma.service.mock'; describe('AppModule', () => { it('should use PrismaServiceMock', async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(PrismaService) .useClass(PrismaServiceMock) .compile(); const prismaService = moduleFixture.get(PrismaService); expect(prismaService).toBeInstanceOf(PrismaServiceMock); }); });
这种方式只会在测试环境中替换服务,不影响开发/生产环境的模块初始化逻辑。
内容的提问来源于stack exchange,提问作者Vladyslav Zavalykhatko
相关产品推荐
相关产品推荐

