0

I am testing my Node.js API using jest. This API is an Express app used for managing tasks. It has signup/login feature to allow only authenticated users to use the app. It has an endpoint for signing up new user and many endpoints are present which uses an express middleware to validate the user authentication through JWT. I have jose-node-cjs-runtime@^3.15.5 package installed which is used for JWT generation and validation.

If I run the project using dev script env-cmd -f ./config/dev.env nodemon src/index.js, there are no issues and it runs fine. I am trying to test user signup using jest test file which uses superagent package to test the endpoint. I am using test script env-cmd -f ./config/test.env jest --watch to run the test. This command is showing following error and test is failing:

 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.

I am unable to figure out why this error is caused. Please help me in finding the solution.

I was using Node.js version 16.7.0 and got this error. I have upgraded my Node.js version today. My current Node.js version is 16.9.0. After upgrading Node.js version also I am receiving this error. Following is the package.json file contents for the project:

{
  "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"
  }
}

Following is the contents of 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');
...

Content of 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);
  });
});
nlern
  • 910
  • 1
  • 8
  • 15
  • could you post your both `./config/dev.env` and './config/test.env' files for reference – Sohan Sep 14 '21 at 05:44
  • @Sohan, I don't think environment files are the issue here. I have run `jest --watch` (instead of `env-cmd -f ./config/test.env jest --watch`) and also received the same error. Also the config files contains secrets so I do not want to post them. – nlern Sep 14 '21 at 05:50
  • I think you are missing configuration of jest, which tell from where to pick module dicrectories and src files? – Sohan Sep 14 '21 at 06:08
  • What is the `""` you have added ? should That follow some path https://jestjs.io/docs/configuration#rootdir-string – Sohan Sep 14 '21 at 06:44
  • I have no need to change this. So I have provided default value which will revert to my current working directory. In the docs same is mentioned https://jestjs.io/docs/configuration#rootdir-string: Note that using '' as a string token in any other path-based config settings will refer back to this value. – nlern Sep 14 '21 at 06:50
  • I have found that `require('jose-node-cjs-runtime/jwt/sign');` this import is causing the issue. If I comment this line and its related code ( do and same for other `jose` imports), then test runs successfully. – nlern Sep 14 '21 at 06:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237079/discussion-between-nlern-and-sohan). – nlern Sep 14 '21 at 07:15
  • I have raised a bug in jest github repo: https://github.com/facebook/jest/issues/11872. The bug was closed with comment that jest does not yet support `exports` which is used by the package `jose-node-cjs-runtime`. – nlern Sep 14 '21 at 08:02

3 Answers3

2

I have raised an issue in jest github repo. The issue was closed with comments:

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

So I read through the comments already logged on similar issue and found this helpful comment which can be used as a work around until jest natively supports this feature.

I found that the jose-node-cjs-runtime module exports field is like following:

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

So I updated the jest config in package.json as follows:

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

After that running the test I did not face any issues.

nlern
  • 910
  • 1
  • 8
  • 15
1

You can use the resolver from this workaround.

I have a test repo with jose being used in all kinds of tools here.

Obviously your setup may vary if you use ts-jest, or ESM test files, or similar. But at that point you should be familiar with the different jest magical options.

> 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();
});

Bottom line - its about making jest require the cjs distribution of the jose module and using the exports mapping. Note that ESM is still not natively supported and neither is the exports mapping, both of which are stable for over a year in all LTS releases of node.

  • 1
    Thanks for the answer. This solution is a bit lengthy as a third part package needs to be installed then a new resolver file needs to be created. However this solution seems to be more cleaner than hard coding the import paths. – nlern Sep 14 '21 at 11:35
0

I think you're missing the configuration of jest and it can be found here

Mainly look for moduleirectories, You can add both relative and absolute paths.

Make sure to include in the roots array, in the modulePaths array, and node_modules in the moduleDirectories array, unless you've got a good reason to exclude them. Here is the sample of jest config, you can include this in package.json file

 "jest": {
  "roots": [
    "<rootDir>",
    "/homeDir/yourLocalDir/path/"
  ],
  "modulePaths": [
    "<rootDir>",
    "/homeDir/yourLocalDir/otherDir/path"
  ],
  "moduleDirectories": [
    "node_modules"
  ],
"testPathIgnorePatterns": ["/node_modules/(?!MODULE_NAME_HERE).+\\.js$"]
}
Sohan
  • 6,252
  • 5
  • 35
  • 56
  • I have added these configurations in `pakcage.json` jest config section but still getting the same error. – nlern Sep 14 '21 at 06:31
  • Update your question with jest config you added, let me check that – Sohan Sep 14 '21 at 06:34
  • I have updated the question with jest config. – nlern Sep 14 '21 at 06:38
  • What happens when you change this line `const app = require('../src/app');` to this `const app = require('app');` And Add `src` to your `"moduleDirectories": [ "node_modules", "src" ],` – Sohan Sep 14 '21 at 06:50
  • Now I am getting following error: Jest encountered an unexpected token Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax. Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. By default "node_modules" folder is ignored by transformers. – nlern Sep 14 '21 at 06:54
  • Try adding following to jest `transformIgnorePatterns: [ "/node_modules/(?!MODULE_NAME_HERE).+\\.js$"],` Replace `MODULE_NAME_HERE` with Your JWT module – Sohan Sep 14 '21 at 07:11
  • I have raised a bug in jest github repo: https://github.com/facebook/jest/issues/11872. The bug was closed with comment that jest does not yet support `exports` which is used by the package `jose-node-cjs-runtime`. – nlern Sep 14 '21 at 08:02