NestJS/TypeORM 单元测试:无法解析 JwtService 的依赖关系
NestJS/TypeORM unit testing: Can't resolve dependencies of JwtService
我正在尝试对该控制器进行单元测试并模拟它需要的 services/repositories。
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
@Post('register')
public async registerAsync(@Body() createUserModel: CreateUserModel) {
const result = await this.authenticationService.registerUserAsync(createUserModel);
// more code here
}
@Post('login')
public async loginAsync(@Body() login: LoginModel): Promise<{ accessToken: string }> {
const user = await this.usersService.getUserByUsernameAsync(login.username);
// more code here
}
}
这是我的单元测试文件:
describe('AuthController', () => {
let authController: AuthController;
let authService: AuthService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
authController = moduleRef.get<AuthenticationController>(AuthenticationController);
authService = moduleRef.get<AuthenticationService>(AuthenticationService);
});
describe('registerAsync', () => {
it('Returns registration status when user registration succeeds', async () => {
let createUserModel: CreateUserModel = {...}
let registrationStatus: RegistrationStatus = {
success: true,
message: 'User registered successfully',
};
jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
Promise.resolve(registrationStatus),
);
expect(await authController.registerAsync(createUserModel)).toEqual(registrationStatus);
});
});
});
但是当运行这个时,我得到以下错误:
● AuthController › registerAsync › Returns registration status when user registration succeeds
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context.
Potential solutions:
- If JWT_MODULE_OPTIONS is a provider, is it part of the current JwtModule?
- If JWT_MODULE_OPTIONS is exported from a separate @Module, is that module imported within JwtModule?
@Module({
imports: [ /* the Module containing JWT_MODULE_OPTIONS */ ]
})
at Injector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:191:19)
at Injector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:147:33)
at resolveParam (../node_modules/@nestjs/core/injector/injector.js:101:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:116:27)
at Injector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:80:9)
at Injector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:37:9)
at Injector.lookupComponentInImports (../node_modules/@nestjs/core/injector/injector.js:223:17)
at Injector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:189:33)
● AuthController › registerAsync › Returns registration status when user registration succeeds
Cannot spyOn on a primitive value; undefined given
48 | };
49 |
> 50 | jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
| ^
51 | Promise.resolve(registrationStatus),
52 | );
53 |
at ModuleMockerClass.spyOn (../node_modules/jest-mock/build/index.js:780:13)
at Object.<anonymous> (Authentication/authentication.controller.spec.ts:50:18)
我不太确定如何继续,所以我需要一些帮助。
我注意到以下几点:
如果您正在测试控制器,则不需要模拟超过一级的深度 pf 服务
你几乎不应该在单元测试中遇到需要 imports
数组的用例。
您可以为您的测试用例做的是类似于以下内容:
beforeEach(async () => {
const modRef = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(),
}
},
{
provide: UserService,
useValue: {
getUserByUsernameAsync: jest.fn(),
}
}
]
}).compile();
});
现在您可以使用 modRef.get()
获取身份验证服务和用户服务,并将它们保存到变量中,以便稍后向它们添加模拟。您可以查看 this testing repository,其中有很多其他示例。
由于您在依赖注入容器中注册 AuthService
并且只是监视 registerUserAsync
,因此还需要注册 JWTService
。
您需要注册在 AuthService
:
中注入的依赖项
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
JWTService, // <--here
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
或注册一个完全模拟的 AuthService
,不需要任何其他依赖项:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(), // <--here
}
},
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
如果您正在为 NestJS 构建一个完整的集成测试套件,那么如果您导入一个导入 AuthService
的 module
,很容易遇到这个错误。这将不可避免地需要 JwtService
,它会出错: Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the RootTestModule context.
这是我解决这个问题的方法。我补充说:
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' }
})
}),
到我的 await Test.createTestingModule({
调用中的 imports: []
函数
最后一件重要的事情是不要直接导入JwtService
。相反,使用上面的代码初始化 JwtModule
,通过扩展本身应该在内部正确初始化 JwtService
。
我正在尝试对该控制器进行单元测试并模拟它需要的 services/repositories。
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
@Post('register')
public async registerAsync(@Body() createUserModel: CreateUserModel) {
const result = await this.authenticationService.registerUserAsync(createUserModel);
// more code here
}
@Post('login')
public async loginAsync(@Body() login: LoginModel): Promise<{ accessToken: string }> {
const user = await this.usersService.getUserByUsernameAsync(login.username);
// more code here
}
}
这是我的单元测试文件:
describe('AuthController', () => {
let authController: AuthController;
let authService: AuthService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
authController = moduleRef.get<AuthenticationController>(AuthenticationController);
authService = moduleRef.get<AuthenticationService>(AuthenticationService);
});
describe('registerAsync', () => {
it('Returns registration status when user registration succeeds', async () => {
let createUserModel: CreateUserModel = {...}
let registrationStatus: RegistrationStatus = {
success: true,
message: 'User registered successfully',
};
jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
Promise.resolve(registrationStatus),
);
expect(await authController.registerAsync(createUserModel)).toEqual(registrationStatus);
});
});
});
但是当运行这个时,我得到以下错误:
● AuthController › registerAsync › Returns registration status when user registration succeeds
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context.
Potential solutions:
- If JWT_MODULE_OPTIONS is a provider, is it part of the current JwtModule?
- If JWT_MODULE_OPTIONS is exported from a separate @Module, is that module imported within JwtModule?
@Module({
imports: [ /* the Module containing JWT_MODULE_OPTIONS */ ]
})
at Injector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:191:19)
at Injector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:147:33)
at resolveParam (../node_modules/@nestjs/core/injector/injector.js:101:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:116:27)
at Injector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:80:9)
at Injector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:37:9)
at Injector.lookupComponentInImports (../node_modules/@nestjs/core/injector/injector.js:223:17)
at Injector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:189:33)
● AuthController › registerAsync › Returns registration status when user registration succeeds
Cannot spyOn on a primitive value; undefined given
48 | };
49 |
> 50 | jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
| ^
51 | Promise.resolve(registrationStatus),
52 | );
53 |
at ModuleMockerClass.spyOn (../node_modules/jest-mock/build/index.js:780:13)
at Object.<anonymous> (Authentication/authentication.controller.spec.ts:50:18)
我不太确定如何继续,所以我需要一些帮助。
我注意到以下几点:
如果您正在测试控制器,则不需要模拟超过一级的深度 pf 服务
你几乎不应该在单元测试中遇到需要
imports
数组的用例。
您可以为您的测试用例做的是类似于以下内容:
beforeEach(async () => {
const modRef = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(),
}
},
{
provide: UserService,
useValue: {
getUserByUsernameAsync: jest.fn(),
}
}
]
}).compile();
});
现在您可以使用 modRef.get()
获取身份验证服务和用户服务,并将它们保存到变量中,以便稍后向它们添加模拟。您可以查看 this testing repository,其中有很多其他示例。
由于您在依赖注入容器中注册 AuthService
并且只是监视 registerUserAsync
,因此还需要注册 JWTService
。
您需要注册在 AuthService
:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
JWTService, // <--here
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
或注册一个完全模拟的 AuthService
,不需要任何其他依赖项:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(), // <--here
}
},
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
如果您正在为 NestJS 构建一个完整的集成测试套件,那么如果您导入一个导入 AuthService
的 module
,很容易遇到这个错误。这将不可避免地需要 JwtService
,它会出错: Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the RootTestModule context.
这是我解决这个问题的方法。我补充说:
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' }
})
}),
到我的 await Test.createTestingModule({
调用中的 imports: []
函数
最后一件重要的事情是不要直接导入JwtService
。相反,使用上面的代码初始化 JwtModule
,通过扩展本身应该在内部正确初始化 JwtService
。