Next.js测试中aws-jwt-verify库无法找到#node-web-compat模块
解决Jest测试Next.js AuthService时aws-jwt-verify的#node-web-compat模块找不到问题
问题概述
用Jest测试Next.js里依赖aws-jwt-verify的AuthService时,终端直接报错找不到#node-web-compat模块,手动跑服务功能正常,但测试就是卡在这里。试过把aws-jwt-verify移去devDependencies、重装依赖都没用,核心需求是mock这个库完成单元测试。
报错信息:
Cannot find module '#node-web-compat' from 'node_modules/aws-jwt-verify/dist/cjs/https.js' Require stack: node_modules/aws-jwt-verify/dist/cjs/https.js node_modules/aws-jwt-verify/dist/cjs/jwk.js node_modules/aws-jwt-verify/dist/cjs/jwt-rsa.js node_modules/aws-jwt-verify/dist/cjs/index.js pages/api/auth/auth.service.ts __tests__/sign-up/api/auth/auth-service.test.ts at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:491:11) at Object.<anonymous> (node_modules/aws-jwt-verify/dist/cjs/https.js:9:28)
问题原因
aws-jwt-verify内部用了私有模块别名#node-web-compat,Jest默认不认识这种内部路径,没法解析;- 当前测试里的
jest.mock写在test函数内部,这会导致mock在模块导入之后才生效,根本没起到拦截作用。
解决方案
第一步:让Jest能解析私有模块别名
直接修改jest.config.js,添加moduleNameMapper把#node-web-compat映射到实际文件路径:
const nextJest = require('next/jest'); const createJestConfig = nextJest({ dir: './' }); const customJestConfig = { moduleDirectories: ['node_modules', '<rootDir>/'], // 新增模块映射,解决#node-web-compat找不到的问题 moduleNameMapper: { '#node-web-compat': '<rootDir>/node_modules/aws-jwt-verify/dist/cjs/node-web-compat.js' } }; module.exports = createJestConfig(customJestConfig);
第二步:修正Mock写法(两种可选方案)
方案一:全局提前Mock(推荐)
把jest.mock移到测试文件最顶部,确保在导入AuthService之前就生效:
// auth-service.test.ts // 提前mock aws-jwt-verify,确保模块导入前就生效 jest.mock('aws-jwt-verify', () => ({ CognitoJwtVerifier: { create: jest.fn().mockReturnValue({ verify: jest.fn().mockResolvedValue({ exp: Math.floor(Date.now() / 1000) + 3600, client_id: process.env.COGNITO_WEB_CLIENT_ID, iss: `https://cognito-idp.${process.env.AWS_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`, token_use: 'access' }) }) } })); // 必须在mock之后导入AuthService import { AuthService } from '../../../pages/api/auth/auth.service'; describe('Auth Service', () => { describe('verifyToken', () => { beforeEach(() => { jest.clearAllMocks(); }); it('验证有效令牌返回true', async () => { const authService = new AuthService(); const result = await authService.verifyToken('valid-token'); expect(result).toBeTruthy(); // 还能验证CognitoJwtVerifier.create的调用参数是否正确 const { CognitoJwtVerifier } = require('aws-jwt-verify'); expect(CognitoJwtVerifier.create).toHaveBeenCalledWith({ userPoolId: process.env.COGNITO_USER_POOL_ID, tokenUse: 'access', clientId: process.env.COGNITO_WEB_CLIENT_ID }); }); it('验证失败时抛出BadRequestException', async () => { // 临时覆盖mock的verify方法,让它抛出错误 const { CognitoJwtVerifier } = require('aws-jwt-verify'); const mockVerify = jest.fn().mockRejectedValue(new Error('无效令牌')); CognitoJwtVerifier.create.mockReturnValue({ verify: mockVerify }); const authService = new AuthService(); await expect(authService.verifyToken('invalid-token')).rejects.toThrow('无效令牌'); }); }); });
方案二:局部SpyOn Mock
如果不想全局mock,也可以用jest.spyOn针对单个测试用例mock:
// auth-service.test.ts import { CognitoJwtVerifier } from 'aws-jwt-verify'; import { AuthService } from '../../../pages/api/auth/auth.service'; import { BadRequestException } from '@storyofams/next-api-decorators'; describe('Auth Service', () => { describe('verifyToken', () => { beforeEach(() => { jest.clearAllMocks(); }); it('验证有效令牌返回true', async () => { // 监听CognitoJwtVerifier.create方法,替换成我们的mock实现 const mockCreate = jest.spyOn(CognitoJwtVerifier, 'create').mockReturnValue({ verify: jest.fn().mockResolvedValue({ exp: Math.floor(Date.now() / 1000) + 3600, client_id: process.env.COGNITO_WEB_CLIENT_ID, iss: `https://cognito-idp.${process.env.AWS_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`, token_use: 'access' }) }); const authService = new AuthService(); const result = await authService.verifyToken('valid-token'); expect(result).toBeTruthy(); expect(mockCreate).toHaveBeenCalledWith({ userPoolId: process.env.COGNITO_USER_POOL_ID, tokenUse: 'access', clientId: process.env.COGNITO_WEB_CLIENT_ID }); }); it('验证失败时抛出BadRequestException', async () => { jest.spyOn(CognitoJwtVerifier, 'create').mockReturnValue({ verify: jest.fn().mockRejectedValue(new Error('令牌过期')) }); const authService = new AuthService(); await expect(authService.verifyToken('expired-token')).rejects.toThrow(BadRequestException); await expect(authService.verifyToken('expired-token')).rejects.toThrow('令牌过期'); }); }); });
额外提示:加载环境变量
如果测试需要用到环境变量,比如COGNITO_USER_POOL_ID,可以在jest配置里加一个setup文件:
- 创建
jest.setup.js:
require('dotenv').config({ path: '.env.local' });
- 修改
jest.config.js:
const customJestConfig = { // ...其他配置 setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], moduleNameMapper: { '#node-web-compat': '<rootDir>/node_modules/aws-jwt-verify/dist/cjs/node-web-compat.js' } };
内容的提问来源于stack exchange,提问作者KamilMastalerz
相关产品推荐
相关产品推荐

