测试Passport Magic策略时createUser方法Jest断言失败
问题:NestJS Passport Magic策略测试中createUser断言失败但日志显示已执行
测试基于Magic SDK实现的NestJS Passport Strategy时出现异常:日志明确显示usersService.createUser已执行,但Jest断言该方法调用次数为0,而usersService.getAuthExistingUser的断言正常通过。推测是jest.spyOn未绑定到正确实例,相关代码、错误信息及测试日志如下:
magic.strategy.ts
import { Magic } from '@magic-sdk/admin' import { Injectable, Logger } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { PassportStrategy } from '@nestjs/passport' import { DoneFunc, Strategy } from 'passport-magic' import { UserFromDid } from '../../types/magic.types' import { UsersService } from '../../users/users.service' @Injectable() export class MagicStrategy extends PassportStrategy(Strategy, 'magic') { private readonly logger = new Logger(MagicStrategy.name) constructor( private readonly config: ConfigService, private readonly userService: UsersService ) { const magic = new Magic(config.get('MAGIC_SECRET_KEY')) super({ magicInstance: magic, }, async function (user: UserFromDid, done: DoneFunc) { const userMetadata = await magic.users.getMetadataByIssuer(user.issuer) const existingUser = await userService.getAuthExistingUser(userMetadata.email) let createdUser: { user_id: string } if (!existingUser) { console.log('TEST IS PASSING HERE') createdUser = await userService.createUser({ email: userMetadata.email, wallet_pubaddress: userMetadata.publicAddress, last_login_at: user.claim.iat, issuer: user.issuer, }) console.log('CREATED USER', createdUser) done(null, existingUser ? { user_id: existingUser.user_id } : createdUser) } else { if (user.claim.iat <= existingUser.last_login_at.getTime() / 1000) { this.logger.error(`Replay attack detected for user ${user.issuer}`) return done(null, false, { message: `Replay attack detected for user ${user.issuer}}.`, }) } await userService.updateLastLogin(user.issuer, user.claim.iat) return done(null, { user_id: existingUser.user_id }) } }, ) } }
magic.strategy.spec.ts
import { ConfigService } from '@nestjs/config' import { Test, TestingModule } from '@nestjs/testing' import { Request } from 'express' import { PrismaService } from '../../prisma.service' import { UsersService } from '../../users/users.service' import { MagicStrategy } from './magic.strategy' describe('MagicStrategy', () => { let magicStrategy: MagicStrategy let usersService: UsersService let configService: ConfigService beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [MagicStrategy, UsersService, ConfigService, PrismaService], }) .overrideProvider(UsersService) .useValue({ getAuthExistingUser: jest.fn(), createUser: jest.fn(), }) .overrideProvider(ConfigService) .useValue({ get: jest.fn() }) .compile() magicStrategy = module.get<MagicStrategy>(MagicStrategy) usersService = module.get<UsersService>(UsersService) configService = module.get<ConfigService>(ConfigService) }) describe('validate', () => { it('should create a new user if the user does not exist', async () => { const user = { issuer: 'someissuer', claim: { iat: 1234567890, }, } const createdUser = { user_id: 'newuserid' } const done = jest.fn() jest.spyOn(usersService, 'getAuthExistingUser').mockResolvedValue(null) jest.spyOn(usersService, 'createUser').mockResolvedValue({ user_id: 'user-id' }) await magicStrategy.authenticate({ headers: { authorization: 'bearer 1234556' } } as Request) expect(configService.get).toHaveBeenCalledWith('MAGIC_SECRET_KEY') expect(usersService.getAuthExistingUser).toHaveBeenCalledWith('email') expect(usersService.createUser).toHaveBeenCalledWith({ email: 'email', wallet_pubaddress: 'publicAddress', last_login_at: user.claim.iat, issuer: user.issuer, }) expect(done).toHaveBeenCalledWith(null, createdUser) }) }) })
错误信息
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: {"email": "email", "issuer": "someissuer", "last_login_at": 1234567890, "wallet_pubaddress": "publicAddress"} Number of calls: 0Jest
测试日志
FAIL src/auth/passport-strategies/magic.strategy.spec.ts ● Console console.log TEST IS PASSING HERE at MagicStrategy.log (auth/passport-strategies/magic.strategy.ts:26:14) console.log CREATED USER { user_id: 'user-id' } at MagicStrategy.log (auth/passport-strategies/magic.strategy.ts:33:14) ● MagicStrategy › validate › should create a new user if the user does not exist expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: {"email": "email", "issuer": "someissuer", "last_login_at": 1234567890, "wallet_pubaddress": "publicAddress"} Number of calls: 0
解决方案
1. 修正回调函数的this绑定与服务引用
构造函数中传给super的验证回调使用普通函数时,会丢失类实例上下文,且直接引用构造函数参数的userService可能导致测试mock无法捕获调用。改为箭头函数并使用this.userService确保引用注入的服务实例:
super({ magicInstance: magic, }, async (user: UserFromDid, done: DoneFunc) => { // 替换为箭头函数 const userMetadata = await magic.users.getMetadataByIssuer(user.issuer) const existingUser = await this.userService.getAuthExistingUser(userMetadata.email) // 使用this.userService let createdUser: { user_id: string } if (!existingUser) { console.log('TEST IS PASSING HERE') createdUser = await this.userService.createUser({ // 使用this.userService email: userMetadata.email, wallet_pubaddress: userMetadata.publicAddress, last_login_at: user.claim.iat, issuer: user.issuer, }) console.log('CREATED USER', createdUser) done(null, existingUser ? { user_id: existingUser.user_id } : createdUser) } else { if (user.claim.iat <= existingUser.last_login_at.getTime() / 1000) { this.logger.error(`Replay attack detected for user ${user.issuer}`) return done(null, false, { message: `Replay attack detected for user ${user.issuer}.`, }) } await this.userService.updateLastLogin(user.issuer, user.claim.iat) // 使用this.userService return done(null, { user_id: existingUser.user_id }) } }, )
2. Mock Magic SDK避免真实API调用
测试中未mockMagic类,导致创建真实实例并调用API,可能引发不可预期的行为。在测试文件顶部添加mock:
import { Magic } from '@magic-sdk/admin' // Mock Magic SDK jest.mock('@magic-sdk/admin', () => { const mockUsersService = { getMetadataByIssuer: jest.fn().mockResolvedValue({ email: 'email', publicAddress: 'publicAddress', }), } return { Magic: jest.fn().mockImplementation(() => ({ users: mockUsersService, })), } })
3. 完善测试中的mock配置
确保configService.get返回假密钥,避免创建Magic实例出错:
beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [MagicStrategy, UsersService, ConfigService, PrismaService], }) .overrideProvider(UsersService) .useValue({ getAuthExistingUser: jest.fn(), createUser: jest.fn(), }) .overrideProvider(ConfigService) .useValue({ get: jest.fn() }) .compile() magicStrategy = module.get<MagicStrategy>(MagicStrategy) usersService = module.get<UsersService>(UsersService) configService = module.get<ConfigService>(ConfigService) // 配置configService返回假密钥 jest.spyOn(configService, 'get').mockReturnValue('fake-magic-secret') })
4. 修正done函数的断言问题
测试中定义的done函数未被authenticate使用,Passport内部会生成自己的done回调。若需断言用户返回结果,可通过authenticate的返回值或mock Passport的回调逻辑,或直接移除无效的done断言。
内容的提问来源于stack exchange,提问作者Jaro
相关产品推荐
相关产品推荐

