You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题原因

  1. aws-jwt-verify内部用了私有模块别名#node-web-compat,Jest默认不认识这种内部路径,没法解析;
  2. 当前测试里的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文件:

  1. 创建jest.setup.js:
require('dotenv').config({ path: '.env.local' });
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 06:09:50