NestJS单元测试:为何不用提供者令牌无法注入模拟AuthService?
问题描述
我在NestJS沙盒项目中编写单元测试时遇到一个问题:注入了authService的AuthImplementation类,在测试中直接模拟AuthService时,authService会显示为undefined;但使用提供者令牌注入后,模拟服务能正常工作。两种注入方式在实际运行环境中都正常,只有单元测试出现这个问题。
相关代码
auth.module.ts
@Module({ imports: [DatabaseModule], controllers: [AuthController], providers: [ { provide: 'UserRepository', useFactory: (databaseAdapterService: DatabaseAdapterService) => databaseAdapterService.getUserRepository(), inject: [DatabaseAdapterService], }, { provide: AuthService, useFactory: (userRepository: UserRepository) => { return new AuthService(userRepository); }, inject: ['UserRepository'], }, { provide: AuthImplementation, useFactory: (authService: IAuthService) => new AuthImplementation(authService), inject: [AuthService], }, ] })
auth.implementation.ts
import { Inject } from '@nestjs/common'; import { User } from 'src/domain/entities/user.entity'; import { AUTH_SERVICE, IAuthService } from 'src/domain/ports/auth-service.interface'; export class AuthImplementation { constructor(@Inject(AUTH_SERVICE) private readonly authService: IAuthService) { } // 下面这种不用提供者令牌的写法,在单元测试中会导致authService为undefined // constructor(private readonly authService: IAuthService) { } async register(username: string, password: string): Promise<User> { return this.authService.register(username, password); } async login(username: string, password: string): Promise<User | null> { return this.authService.login(username, password); } }
auth.implementation.spec.ts
describe('AuthImplementation', () => { let authImplementation: AuthImplementation; let mockAuthService: jest.Mocked<IAuthService>; beforeEach(async () => { mockAuthService = { register: jest.fn(), login: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ AuthImplementation, { // 这种写法不生效 //provide: AuthService, // 这种写法正常工作 provide: AUTH_SERVICE, useValue: mockAuthService, }, ], }).compile(); authImplementation = module.get<AuthImplementation>(AuthImplementation); }); describe('register', () => { it('should register a user and return the user object', async () => { const username = 'testuser'; const password = 'testpass'; const mockUser = new User(new ObjectId(), username, password); mockAuthService.register.mockResolvedValue(mockUser); const result = await authImplementation.register(username, password); expect(mockAuthService.register).toHaveBeenCalledWith(username, password); expect(result).toBe(mockUser); }); });
我的问题是:
- 是否可以不用定义
export const AUTH_SERVICE = Symbol('AUTH_SERVICE');这样的提供者令牌,直接在测试模块中引用AuthService? - 为何使用
provide: AUTH_SERVICE能正常工作,而provide: AuthService会导致AuthImplementation中的authService显示为undefined?
问题解答
为什么provide: AuthService不生效?
核心原因是你在AuthImplementation的构造函数里明确用@Inject(AUTH_SERVICE)指定了注入令牌,而非依赖AuthService类本身作为注入标识。
NestJS的依赖注入系统完全基于「令牌匹配」:
- 当构造函数中使用
@Inject(TOKEN)时,Nest会严格查找provide值等于TOKEN的提供者 - 只有当你不写
@Inject时,Nest才会默认把类本身作为注入令牌(比如constructor(private authService: AuthService))
你当前的代码里,AuthImplementation明确要求注入AUTH_SERVICE令牌对应的实例,所以测试模块里如果只提供provide: AuthService的模拟,Nest找不到匹配的令牌,自然会注入undefined。
能不能不用令牌直接引用AuthService?
可以,但需要同步调整三处代码:
- 修改
AuthImplementation的构造函数:去掉@Inject(AUTH_SERVICE),直接依赖AuthService类(需确保AuthService实现了IAuthService接口):
// auth.implementation.ts import { AuthService } from './path-to-auth-service'; // 导入实际的AuthService类 export class AuthImplementation { constructor(private readonly authService: AuthService) { } // 直接注入类 // 业务方法保持不变 async register(username: string, password: string): Promise<User> { return this.authService.register(username, password); } }
- 调整测试模块的提供者:
// auth.implementation.spec.ts beforeEach(async () => { mockAuthService = { register: jest.fn(), login: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ AuthImplementation, { provide: AuthService, // 现在用AuthService类作为令牌 useValue: mockAuthService, }, ], }).compile(); authImplementation = module.get<AuthImplementation>(AuthImplementation); });
- 同步更新
AuthModule中的AuthImplementation提供者:
// auth.module.ts { provide: AuthImplementation, useFactory: (authService: AuthService) => // 这里改为AuthService类型 new AuthImplementation(authService), inject: [AuthService], // 注入令牌改为AuthService类 },
额外说明
如果你想保持「依赖接口而非具体类」的设计(符合依赖倒置原则),那使用提供者令牌是更规范的做法。这种情况下,测试时必须用对应的令牌(AUTH_SERVICE)来提供模拟,否则Nest无法匹配到正确的依赖。
内容的提问来源于stack exchange,提问作者Jas Singh
相关产品推荐
相关产品推荐

