Testing with Jest and Webpack aliases - javascript

I am looking to be able to use webpack aliases to resolve imports when using jest, and optimally, reference the webpack.aliases to avoid duplication.
Jest conf:
"jest": {
"modulePaths": ["src"],
"moduleDirectories": ["node_modules"],
"moduleNameMapper": {
"^#shared$": "<rootDir>/shared/",
"^#components$": "<rootDir>/shared/components/"
}
},
Webpack aliases:
exports.aliases = {
'#shared': path.resolve(paths.APP_DIR, 'shared'),
'#components': path.resolve(paths.APP_DIR, 'shared/components'),
};
Imports:
import Ordinal from '#shared/utils/Ordinal.jsx';
import Avatar from '#components/common/Avatar.jsx';
For some reason the # causes issues, so when removed (in both alias and import), it can find shared but components still cannot be resolved.
FAIL src/shared/components/test/Test.spec.jsx
● Test suite failed to run
Cannot find module '#shared/utils/Ordinal.jsx' from 'Test.jsx'
I have tried using jest-webpack-alias, babel-plugin-module-resolver and the Jest/Webpack docs

This seems to have been fixed.
Below is a working setup:
Versions
"jest": "~20.0.4"
"webpack": "^3.5.6"
package.json
"jest": {
"moduleNameMapper": {
"^#root(.*)$": "<rootDir>/src$1",
"^#components(.*)$": "<rootDir>/src/components$1",
}
}
webpack.shared.js
const paths = {
APP_DIR: path.resolve(__dirname, '..', 'src'),
};
exports.resolveRoot = [paths.APP_DIR, 'node_modules'];
exports.aliases = {
'#root': path.resolve(paths.APP_DIR, ''),
'#components': path.resolve(paths.APP_DIR, 'components'),
};

Since I had the same problem before I read again, and this time more carefully the documentation. Correct config should be:
"jest": {
"moduleNameMapper": {
"^#shared(.*)$": "<rootDir>/shared$1",
"^#components(.*)$": "<rootDir>/shared/components$1"
}
},

Using: "jest": "^26.5.3", and "webpack": "4.41.5",
I was able to properly match my webpack/typescript aliases in the jest.config.js with this pattern:
Webpack config:
module.exports = {
// the rest of your config
resolve: {
alias: {
'components': path.resolve(__dirname, 'js/app/components'),
'modules': path.resolve(__dirname, 'js/app/modules'),
'types': path.resolve(__dirname, 'js/types'),
'hooks': path.resolve(__dirname, 'js/app/hooks'),
'reducers': path.resolve(__dirname, 'js/app/reducers'),
'__test-utils__': path.resolve(__dirname, 'js/app/__test-utils__')
}
},
}
Jest.config.js:
moduleNameMapper: {
'^types/(.*)$': '<rootDir>/js/types/$1',
'^components/(.*)$': '<rootDir>/js/app/components/$1',
'^modules/(.*)$': '<rootDir>/js/app/modules/$1',
'^hooks/(.*)$': '<rootDir>/js/app/hooks/$1',
'^reducers/(.*)$': '<rootDir>/js/app/reducers/$1',
'^__test-utils__/(.)$': '<rootDir>/js/app/__test-utils__/$1'
}
Here's an explanation of the symbols:
(.*)$: capture whatever comes after the exact match (the directory)
$1: map it to this value in the directory I specify.
and tsconfig.json:
"paths": {
"config": ["config/dev", "config/production"],
"components/*": ["js/app/components/*"],
"modules/*": ["js/app/modules/*"],
"types/*": ["js/types/*"],
"hooks/*": ["js/app/hooks/*"],
"reducers/*": ["js/app/reducers/*"],
"__test-utils__/*": ["js/app/__test-utils__/*"]
}

For anyone using # as the root of their modules, you have to be more specific since other libs can use the # in node modules.
moduleNameMapper: {
"^#/(.*)$": "<rootDir>/src/$1"
},
It translates to "anything that matches #/ should be sent to <rootDir>/src/<rest of the path>

FWIW, Try switching the alias order, keep the more specific up and less specific down, e.g.
"moduleNameMapper": {
"^#components$": "<rootDir>/shared/components/",
"^#shared$": "<rootDir>/shared/"
}

This is a real insanity but when I try to map module in package.json like this:
"jest": {
"moduleNameMapper": {
'#root/(.*)': '<rootDir>/../$1'
}
}
It doesn't work.
But when I do the same in jest.config.js it works:
module.exports = {
moduleNameMapper: {
'#root/(.*)': '<rootDir>/../$1'
}
}

None of the suggested answers worked for me.
Was able to solve it with the following pattern:
Jest version 25+
moduleNameMapper: {
'^Screens(.*)': '<rootDir>/src/screens/$1',
'^Components(.*)': '<rootDir>/src/components/$1',
'^Assets(.*)': '<rootDir>/src/assets/$1',
'^Services/(.*)': '<rootDir>/src/services/$1',
},

this solution works well for me:
moduleNameMapper: {
'^Screens(.*)': '<rootDir>/src/screens/$1',
'^Components(.*)': '<rootDir>/src/components/$1',
'^Assets(.*)': '<rootDir>/src/assets/$1',
'^Services/(.*)': '<rootDir>/src/services/$1',
},

Assume your webpack aliases lies in webpack.yml in "resolved_paths" block.
To add same aliases in jest, if your jest config is in package.json, use moduleDirectories:
"jest":{
"moduleDirectories":[
".",
"<rootDir>/shared/components/",
"<rootDir>/shared/",
"node_modules"
]
}

Related

How to set up Jest w/ESM to recognize non-cjs modules in node_modules

Have got a successful jest/esm setup, however occasionally a module is released that specifies both a main key (for commonjs) and a module key (for ESM) in its package.json. This leads to jest errors, for example with the uuid module:
/repo/path/node_modules/uuid/dist/esm-browser/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
^^^^^^
SyntaxError: Unexpected token 'export'
I can see that dist/esm-browser/index.js is the file specified by the module key in package.json.
How can Jest w/ESM be configured to handle these cases, where stuff in node_modules is ESM?
Jest config:
{
"resetMocks": true,
"testEnvironment": "jsdom",
"testMatch": [
"**/src/**/*.(spec|test).[tj]s?(x)"
],
"preset": "ts-jest/presets/default-esm",
"extensionsToTreatAsEsm": [
".ts",
".tsx"
],
"globals": {
"ts-jest": {
"useESM": true
}
},
"globalSetup": "<rootDir>/jest/setup.cjs",
"globalTeardown": "<rootDir>/jest/teardown.cjs",
"watchPathIgnorePatterns": [
"<rootDir>/.tmp"
],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1",
"^~components/(.*)$": "<rootDir>/src/components/$1",
"^~util/(.*)$": "<rootDir>/src/util/$1",
"^~types/(.*)$": "<rootDir>/src/types/$1"
}
}
If transformIgnorePatterns doesn't work for some reason, you can solve it with moduleNameMapper.
moduleNameMapper: {
// '^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
'^uuid$': require.resolve('uuid'),
}
I've had the same problem and it was fixed the same way as mentioned in this comment: https://github.com/nrwl/nx/issues/812#issuecomment-429420861 in my jest.config.js:
// list to add ESM to ignore
const esModules = ['uuid'].join('|');
// ...
module.exports = {
//...
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
// ...
};

Importing local files in `jest.config.js` file

In my jest.config.js file, I need to populate globals property. For populating the globals property I need to require local modules, as shown below:
const path = require('path')
const server = require('./server/cfg')
module.exports = {
rootDir: path.resolve(__dirname),
moduleFileExtensions: [
'js',
'json',
'vue',
'ts'
],
moduleNameMapper: {
'^#/(.*)$': '<rootDir>/src/$1'
},
transform: {
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.(js|jsx)?$": "<rootDir>/node_modules/babel-jest",
"^.+\\.ts$": "<rootDir>/node_modules/ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
snapshotSerializers: [
"jest-serializer-vue"
],
setupFiles: [
"<rootDir>/globals.js"
],
testEnvironment: "jsdom",
globals: {
server: {
server
}
}
}
Whit this configuration, I get the following error:
Error: Cannot find module './server/cfg'
This is my folder structure
server/
cfg.ts
src/
jest.config.js
webpack.config.js
However, I can require node's built-in modules. I'm not able to figure out why it is happening. Any ideas on how I can overcome this?
Jest is initiated by node, not ts-node and it's not able to resolve the .ts file by default.
Probably adding setupFilesAfterEnv will help you.
jest.config.js
module.exports = {
// ... your config
setupFilesAfterEnv: [
"<rootDir>/environmentWithServer.ts`
]
}
environmentWithServer.ts
global.server = require('./server/cfg');

Babel and Webpack are throwing "Can't resolve 'regenerator-runtime/runtime'"

I'm working on a browser-based project that needs to be IE11-compatible (sigh). Webpack is choking on async/await. Here is my console's output:
Based on your code and targets, added regenerator-runtime.
...
Module not found: Error: Can't resolve 'regenerator-runtime/runtime'
I've looked at many SO questions similar to mine, without luck. Many recommend using #babel/polyfill which I am avoiding since it has been deprecated.
What is causing this issue? I expect it could be fixed by manually importing regenerator-runtime/runtime, but it seems one of the main selling points of babel-env is NOT having to manually import polyfills, so I assume I'm missing a step. Thank you!
Here is what I am attempting to run, which is being imported into another file:
class Fetcher {
constructor() {
this.form = document.querySelector('form');
this.form.addEventListener('submit', this.onSubmit.bind(this));
}
async onSubmit(event) {
event.preventDefault();
const apiResponse = await fetch(`${WP_url}/api`);
const apiJson = await apiResponse.json();
console.log(apiJson);
}
}
export default Fetcher;
webpack.config.js:
const path = require('path');
function pathTo(filepath) {
return path.join(__dirname, filepath);
}
module.exports = function(env, argv) {
const isProd = Boolean(argv.mode === 'production');
const config = {
entry: {
index: [
pathTo('index.js'),
],
},
externals: {
jquery: 'jQuery',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'#babel/preset-env',
{
corejs: 3,
debug: true,
targets: {
browsers: [
'IE 11',
],
},
useBuiltIns: 'usage',
},
],
],
},
},
],
},
optimization: {
minimize: isProd,
},
output: {
filename: '[name].js',
path: pathTo('web'),
},
};
return config;
};
package.json
{
"private": true,
"dependencies": {
"core-js": "^3.4.1",
"focus-within-polyfill": "^5.0.5"
},
"devDependencies": {
"#babel/core": "^7.7.2",
"#babel/preset-env": "^7.7.1",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"css-loader": "^3.2.0",
"eslint": "^6.6.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
},
"scripts": {
"dev": "webpack --mode=development --display-modules",
"dev:watch": "npm run dev -- --watch",
"prod": "webpack --mode=production --display-modules",
"prod:watch": "npm run prod -- --watch"
}
}
Simply running npm i regenerator-runtime fixed it, actually.
With useBuiltIns: 'usage', having all the import statements wasn't necessary I guess.
¯\_(ツ)_/¯
Just add import 'regenerator-runtime/runtime' in the file where you have the async/await.
In my case, your answer wasn't enough and I needed to set babel sourceType to unambiguous as suggested here to allow a correct compilation of the project. This option was required because the #babel/runtime/regenerator/index.js file exports its reference using module.exports = require("regenerator-runtime"); that breaks ES6 compilation.
Another useful note resolving a similar but unrelated compilation issue was to use /node_modules\/(css-loader|#babel|core-js|promise-polyfill|webpack|html-webpack-plugin|whatwg-fetch)\// as exclude for the babel rule to avoid resolution loops without loosing external libraries compilation (when necessary) as suggested here.
TL;DR
In your specific case the babel rule would become:
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
sourceType: 'unambiguous',
presets: [
['babel/preset-env', {
corejs: 3,
debug: true,
targets: {
browsers: [ 'IE 11', ],
},
useBuiltIns: 'usage',
}],
],
},
},
I started getting this error when added transpileDependencies: ['openplayerjs'], to my vue.config.js, as Babel with #vue/cli-plugin-babel/preset won't touch dependencies in node_modules, go figure why. Installing regenerator-runtime did not help, and I thought the issue lied in Yarn's pnp modules:
This dependency was not found:
* regenerator-runtime/runtime.js in /Volumes/Backup/home/Documents/.yarn/unplugged/openplayerjs-npm-2.9.3-aa4692035d/node_modules/openplayerjs/dist/esm/media.js, /Volumes/Backup/home/Documents/.yarn/unplugged/openplayerjs-npm-2.9.3-aa4692035d/node_modules/openplayerjs/dist/esm/media/ads.js and 1 other
To install it, you can run: npm install --save regenerator-runtime/runtime.js
so I tried unplugging everything via yarn unplug <module_name>, still did not work.
Eventually changing useBuiltIns: 'usage' to useBuiltIns: 'entry' in babel.config.js solved it straight away.
Following the official documentation:
Install required packages.
npm install --save-dev #babel/plugin-transform-runtime
npm install --save #babel/runtime
Add plugin to the configuration file.
{
"plugins": ["#babel/plugin-transform-runtime"]
}
This is officially recommended method, more available in the mentioned documentation.

Adding a module mapper to Jest?

The tsconfig.json has paths setup like this:
"paths": {
"#fs/*": ["src/*"],
"#test/*": ["test/*"]
}
With that all #fs/blah/blah imports are resolving fine in VSCode. In order to get Jest to work with the same import declaration I added moduleMapper to jest.config.ts and this is the full config:
module.exports = {
roots: ["./src"],
transform: {
"^.+\\.tsx?$": "ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
moduleNameMapper: {
"#fs/*": ["src/*"]
}
};
With that statements like import { isBoolean } from "#fs/is"; do not resolve.
When changed using a relative import it Jest does resolve it (import { isBoolean } from "./is";)
Thoughts?
According to the jest document, keys of moduleNameMapper are regular expressions. (not glob patterns)
And <rootDir> can be used to reference the root of the project. (docs)
So, we can configure like below:
moduleNameMapper: {
"^#fs/(.*)$": "<rootDir>/src/$1"
}

webpack imported module is not a constructor

I created a small JS module which I intend to make an npm package, but for now is just on GitHub. This module is written in ES6 and SCSS, and is thus relying on webpack and babel for transpilation.
To test it, I created a separate project with a similar setup (webpack and babel). After npm installing my module, when trying to import it into my index.js, I get the following error in Chrome Developer Tools: (with x being my module's name)
index.js:11 Uncaught TypeError: x__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor
at eval (index.js:11)
at Object../src/index.js (main.js:368)
at __webpack_require__ (main.js:20)
at eval (webpack:///multi_(:8081/webpack)-dev-server/client?:2:18)
at Object.0 (main.js:390)
at __webpack_require__ (main.js:20)
at main.js:69
at main.js:72
I've looked through countless answers and tried countless solutions, to no avail. My module's setup is as follows.
.babelrc
{
"presets": [
["env", {
"targets": {
"browsers": ["ie >= 11"]
}
}]
],
"plugins": [
"transform-es2015-modules-commonjs",
"transform-class-properties"
]
}
webpack.common.js
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')
const baseSCSS = new ExtractTextPlugin('main/_base.css')
const themeSCSS = new ExtractTextPlugin('main/_theme.css')
module.exports = {
entry: {
example: [
path.join(__dirname, 'src', 'example', 'index.js')
],
main: [
'idempotent-babel-polyfill',
path.join(__dirname, 'src', 'index.js')
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: path.join('[name]', 'index.js')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract(
{
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}
)
},
{
test: /\_base-scss$/,
use: baseSCSS.extract(
{
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}
)
},
{
test: /\_theme-scss$/,
use: themeSCSS.extract(
{
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}
)
}
]
},
plugins: [
new cleanWebpackPlugin('dist', {}),
new ExtractTextPlugin({ filename: path.join('example', 'style.css') }),
baseSCSS,
themeSCSS,
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: path.join(__dirname, 'src', 'example', 'index.html'),
filename: path.join('example', 'index.html')
})
]
}
webpack.prod.js
const merge = require('webpack-merge')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const webpack = require('webpack')
const common = require('./webpack.common.js')
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
],
mode: 'production'
})
package.json
{
"name": "my-module-name",
"version": "1.0.0-beta.1",
"description": "",
"main": "dist/main/index.js",
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"server": "node src/server",
"format": "prettier-standard 'src/**/*.js'",
"lint": "eslint src",
"build": "webpack --config webpack.prod.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Liran",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-preset-env": "^1.7.0",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"html-webpack-plugin": "^3.2.0",
"idempotent-babel-polyfill": "^0.1.1",
"node-sass": "^4.9.0",
"prettier-standard": "^8.0.1",
"sass-loader": "^7.0.1",
"style-loader": "^0.21.0",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "^4.6.0",
"webpack-cli": "^2.0.15",
"webpack-dev-middleware": "^3.1.3",
"webpack-dev-server": "^3.1.3",
"webpack-merge": "^4.1.2"
}
}
Any help/pointers would be greatly appreciated. If you need more information, please let me know.
If you are not the library author and are having a problem consuming another library, you may be seeing an error like this:
TypeError: [LIBRARY_NAME]__WEBPACK_IMPORTED_MODULE_3__ is not a constructor
If that's the case, you may be importing the library incorrectly in your code (it may be a problem with default exports). Double check the library docs for usage.
It may be as simple as changing this:
import Foo from 'some-library/Foo';
to this:
import { Foo } from 'some-library';
It is not working because it is missing libraryTarget and library properties. By doing that webpack know which format of module you would like to create, i.e: commonjs (module.exports) or es (export).
I would do something like:
...
output: {
path: path.join(__dirname, 'dist'),
filename: path.join('[name]', 'index.js'),
library: "my-library",
libraryTarget: "umd" // exposes and know when to use module.exports or exports.
},
...
Besides setting the libraryTarget, it may also be necessary to move the export in the JavaScript file to the default.
function MyClassName() {
...
}
export default MyClassName;
And then in the webpack configuration the library type umd ...
(Note that I have used the newer library.type instead the older libraryTarget (see https://webpack.js.org/configuration/output/#outputlibrarytarget).
const path = require('path');
module.exports = {
mode: "production",
entry: '../wherever/MyClassName.js',
output: {
library: {
name: "MyClassName",
type: "umd", // see https://webpack.js.org/configuration/output/#outputlibrarytype
export: "default", // see https://github.com/webpack/webpack/issues/8480
},
filename: 'MyClassName.min.js',
path: path.resolve(__dirname, '../wherever/target/')
},
optimization: {
minimize: true
}
};
The export default makes the class available in JavaScript like the file was embedded directly, i.e.,
<script type="text/javascript" src="MyClassName.min.js"></script>
<script type="text/javascript">
<!--
var myInstance = new MyClassName();
// -->
</script>
Disclaimer: I added this answer even though the original question is three years old by now. After encountering the "is not a constructor" issue, it took me hours of searching to find the default solution. And that was the second time, I searched and found it :D
Cf. David Calhoun's answer, if you run into this with a third-party library, you may be trying to import a CommonJS module as an ECMAScript module. The workaround there seems to be to use require instead of import, e.g., instead of
import { Foo } from 'bar'
you need to write
const Foo = require('bar')
(There may be a more elegant way to handle this, but this is what worked for me.)
For me, it was the cache issue. Just cleared the cookies, cache data and closed, reopened the browser. It worked.
In my case, the error was being caused in React when trying to invoke JS's built-in Error constructor, or in other words, basically when calling throw new Error("something").
On inspection of my code, I realised I had a component called Error in my project which was being imported into the same file. The name clash between this component and JS's built-in Error constructor was causing the error mentioned in the question.
In case something is using wepack 5 + babel 7
"webpack": "5.73.0",
"#babel/core": "7.4.4",
"#babel/preset-env": "7.4.4",
"babel-loader": "8.0.5",
AND want to use class instead function, this worked for me:
class Person {
constructor(fname, lname, age, address) {
this.fname = fname;
this.lname = lname;
this.age = age;
this.address = address;
}
get fullname() {
return this.fname +"-"+this.lname;
}
}
export default Person;
In my case .babelrc was not necesary
tl;dr
Make sure that you import properly through index files.
Explanation
For me, this error was caused by importing through index files. I had multiple directories with their index.ts files that exported all the files inside the directory. These index files were accumulated/reexported by a main index.ts file so everything can be imported through it.
src/
├── index.ts
├── module1/
│ ├── index.ts
│ ├── file1.ts
│ └── file2.ts
└── module2/
├── index.ts
├── file3.ts
└── file4.ts
In file4.ts I had an import like this:
import { file1Class, file2Class, file3Class } from "src";
I had to split it into two separate imports:
import { file1Class, file2Class } from "src/module1";
import { file3Class } from "src/module2";

Categories

Resources