jest fails with error: Cannot find module 'jose-node-cjs-runtime/jwt/sign'

jest fails with error: Cannot find module 'jose-node-cjs-runtime/jwt/sign'

我正在用玩笑测试我的 Node.js API。 API 是一个用于管理任务的 Express 应用程序。它具有 signup/login 功能,仅允许经过身份验证的用户使用该应用程序。它有一个用于注册新用户的端点,并且存在许多使用快速中间件通过 JWT 验证用户身份验证的端点。我安装了 jose-node-cjs-runtime@^3.15.5 包,用于 JWT 生成和验证。

如果我 运行 使用 dev 脚本 env-cmd -f ./config/dev.env nodemon src/index.js 的项目,没有问题,运行 没问题。我正在尝试使用 jest 测试文件来测试用户注册,该文件使用 superagent 包来测试端点。我正在使用测试脚本 env-cmd -f ./config/test.env jest --watch 来 运行 测试。此命令显示以下错误并且测试失败:

 FAIL  tests/user.test.js
  ● Test suite failed to run

    Cannot find module 'jose-node-cjs-runtime/jwt/sign' from 'src/models/user.js'

    Require stack:
      src/models/user.js
      src/routers/user.js
      src/app.js
      tests/user.test.js

      3 | const bcrypt = require('bcryptjs');
      4 | // library for signing jwt
    > 5 | const { SignJWT } = require('jose-node-cjs-runtime/jwt/sign');
        |                     ^
      6 | // library for generating symmetric key for jwt
      7 | const { createSecretKey } = require('crypto');
      8 | // task model

      at Resolver.resolveModule (node_modules/jest-resolve/build/resolver.js:313:11)
      at Object.<anonymous> (src/models/user.js:5:21)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        3.385 s
Ran all test suites related to changed files.

我无法弄清楚为什么会导致此错误。请帮助我找到解决方案。

我使用的是 Node.js 版本 16.7.0,但遇到了这个错误。我今天升级了 Node.js 版本。我当前的 Node.js 版本是 16.9.0。升级 Node.js 版本后,我也收到此错误。以下是项目的 package.json 文件内容:

{
  "name": "task-manager",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "engines": {
    "node": ">=16.7.0"
  },
  "scripts": {
    "start": "node src/index.js",
    "dev": "env-cmd -f ./config/dev.env nodemon src/index.js",
    "test": "env-cmd -f ./config/test.env jest --watch"
  },
  "jest": {
    "testEnvironment": "node",
    "roots": [
      "<rootDir>"
    ],
    "modulePaths": [
      "<rootDir>"
    ],
    "moduleDirectories": [
      "node_modules"
    ]
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@sendgrid/mail": "^7.4.6",
    "bcryptjs": "^2.4.3",
    "express": "^4.17.1",
    "jose-node-cjs-runtime": "^3.15.5",
    "mongodb": "^4.1.1",
    "mongoose": "^6.0.2",
    "multer": "^1.4.3",
    "sharp": "^0.29.0",
    "validator": "^13.6.0"
  },
  "devDependencies": {
    "env-cmd": "^10.1.0",
    "jest": "^27.1.0",
    "nodemon": "^2.0.12",
    "supertest": "^6.1.6"
  }
}

以下是src/models/user.js的内容:

const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
// library for signing jwt
const { SignJWT } = require('jose-node-cjs-runtime/jwt/sign');
// library for generating symmetric key for jwt
const { createSecretKey } = require('crypto');
// task model
const Task = require('./task');
...

src/tests/user.test.js的内容:

const request = require('supertest');
const app = require('../src/app');

describe('user route', () => {
  test('should signup a new user', async () => {
    await request(app)
      .post('/users')
      .send({
        name: '__test_name__',
        email: '__test_email__',
        password: '__test_password__',
      })
      .expect(201);
  });
});

我认为你缺少 jest 的配置,可以找到它 here

主要寻找moduleirectories, 您可以添加相对路径和绝对路径。

确保包含在 roots 数组、modulePaths 数组和 node_modules moduleDirectories 数组中,除非您有充分的理由将它们排除在外。 这是 jest 配置的示例,您可以将其包含在 package.json 文件

 "jest": {
  "roots": [
    "<rootDir>",
    "/homeDir/yourLocalDir/path/"
  ],
  "modulePaths": [
    "<rootDir>",
    "/homeDir/yourLocalDir/otherDir/path"
  ],
  "moduleDirectories": [
    "node_modules"
  ],
"testPathIgnorePatterns": ["/node_modules/(?!MODULE_NAME_HERE).+\.js$"]
}

我开玩笑地提出了一个 issue github 回购协议。该问题已关闭并发表评论:

The module uses exports which isn't supported yet: #9771

所以我通读了已经记录在类似问题上的评论,发现了 this 有用的评论,在 jest 原生支持此功能之前,可以将其用作解决方法。

我发现 jose-node-cjs-runtime 模块 exports 字段如下所示:

"./jwt/sign": "./dist/node/cjs/jwt/sign.js",
"./jwt/verify": "./dist/node/cjs/jwt/verify.js",

所以我更新了 package.json 中的 jest 配置如下:

"jest": {
  "testEnvironment": "node",
  "moduleNameMapper": {
    "^jose-node-cjs-runtime/(.*)$": "jose-node-cjs-runtime/dist/node/cjs/"
  }
}

在那之后运行测试我没有遇到任何问题。

您可以使用 this workaround 中的解析器。

我有一个测试仓库 jose 用于各种工具 here

显然,如果您使用 ts-jest、ESM 测试文件或类似文件,您的设置可能会有所不同。但到那时你应该熟悉不同的笑话魔法选项了。

> jest --resolver='./temporary_resolver.js' 'jest.test.*'

 PASS  ./jest.test.js
  ✓ it works in .js (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.367 s, estimated 1 s
Ran all test suites matching /jest.test.*/i.
// temporary workaround while we wait for https://github.com/facebook/jest/issues/9771

const resolver = require('enhanced-resolve').create.sync({
  conditionNames: ['require'],
  extensions: ['.js', '.json', '.node', '.ts']
})

module.exports = function (request, options) {
  return resolver(options.basedir, request)
}
const { generateSecret } = require("jose/util/generate_secret");

test('it works in .js', async () => {
  expect(typeof generateSecret).toBe('function');
  expect(await generateSecret('HS256')).toBeTruthy();
});

底线 - 它是关于让 jest 需要 jose 模块的 cjs 分发并使用导出映射。请注意,ESM 仍然不受本地支持,导出映射也不受支持,这两者在节点的所有 LTS 版本中都稳定了一年多。