I'm trying to create an automatic mock using jest in my ES6 javascript project.
I'm using node v15.0.1, and jest 26.6.0 on ubuntu 18.04.5.
I have a test file containing the following code:
import RenderBuffer from './renderbuffer.js'
jest.mock('./renderbuffer.js');
beforeEach(() => {
RenderBuffer.mockClear();
});
When I run the test I run into the following issue:
ReferenceError: require is not defined
4 |
5 | beforeEach(() => {
> 6 | RenderBuffer.mockClear();
| ^
7 | });
8 |
The error is surprising to me as I'm not using a require statement.
My package.json config contains the following:
"type": "module",
"main": "src/index.js",
"devDependencies": {
"jest": "^26.5.3",
"jest-canvas-mock": "^2.3.0"
},
"jest": {
"setupFiles": ["jest-canvas-mock"]
},
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"test-coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
}
Any ideas as to what the root cause of this issue is?
You have to disable any source code transformations in order to make it working by setting
{
transform: {}
}
in your Jest configuration file. By default transform option is configured to use babel-jest. Please refer to this Jest documentation section for more details. Also note that you should import jest explicitly:
import { jest } from '#jest/globals';
Unfortunatelly, it can still have some issues running your tests as other commenters already mentioned. Probably, one should follow this issue to keep tracking changes being made in Jest for ESM support.
For example, I was unlucky to mock static module imports at the moment (version 26.6.2):
jest.(do|un)mock
Since ESM has different "stages" when evaluating a module, jest.mock will not work for static imports. It can work for dynamic imports though, so I think we just have to be clear in the docs about what it supports and what it doesn't.
jest.mock calls are hoisted, but that doesn't help in ESM. We might consider transforming import 'thing' to import('thing') which should allow hoisting to work, but then it's async. Using top-level await is probably a necessity for such an approach. I also think it's invasive enough to warrant a separate option. Something to discuss - we don't need to support everything jest.mock can for for an initial release.
Related
I followed a JavaScript unit testing tutorial at acadamind.com in that tutorial instructor used Vitest for demonstrations and the reason they mentioned using Vitest instead of Jest was Jest needed some extra configuration to work with the latest JavaScript syntax.
After doing my own research about unit testing I realized industry demand unit testing skills with the Jest. So I followed another tutorial for learning unit testing with Jest and React Testing Library (RTL).
I created a brand new TypeScript project with Create React App (CRA) and followed the instructions in that tutorial and everything went well. The instructor mentioned that Jest and RTL are supported out of the box with CRA.
After studying unit testing I tried to apply that knowledge and write some tests with my application, which was created some time back, and recently we updated it to React Scripts 5. In that application, I check node_modules folder, and Jest is there as a dependency. But I have noticed that the following packages are not listed in the package.json file in my project, so I installed them:
#testing-library/jest-dom": "^5.16.5",
#testing-library/react": "^13.4.0",
#testing-library/user-event": "^14.4.3",
#types/jest": "^29.4.0",
After that, I noticed my new project has this file in the src folder so I have copy pasted this file as well.
setupTest.ts
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '#testing-library/jest-dom';
When I tried to run when I tried to write my first unit test I noticed that VS Code doesn't recognize this test function as a global function and indicates an error and then I try to run the test script and I am getting this 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.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Details:
/home/pathum/Documents/tagd/node_modules/axios/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import axios from './lib/axios.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
> 1 | import axios from 'axios';
| ^
2 | // config
3 | import { HOST_API } from '../config';
4 |
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
at Object.<anonymous> (src/utils/axios.ts:1:1)
at Object.<anonymous> (src/hooks/useRefresh.tsx:2:1)
at Object.<anonymous> (src/hooks/useAxiosPrivate.tsx:3:1)
at Object.<anonymous> (src/pages/contracts/contract-form/new-contact-person.tsx:12:1)
at Object.<anonymous> (src/pages/contracts/contract-form/parties.tsx:15:1)
at Object.<anonymous> (src/pages/contracts/contract-form/contract-form.tsx:9:1)
at Object.<anonymous> (src/pages/contracts/contract-form/contract-form.test.tsx:2:1)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:404:19)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 2.398 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
Seems I need to do some configurations for Jest to work properly in the application. How do I fix this?
Install the necessary dependencies: npm install --save-dev #babel/preset-typescript and npm install --save-dev jest-cli typescript
Create a new file in the root of your project called jest.config.js and add the following content to it:
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
};
Add a new property called "jest" to the "scripts" section of your package.json file and set it to the following: "jest --config jest.config.js --coverage". This will tell Jest to use the configuration file you just created and also generate a coverage report.
Create a new file in the root of your project called tsconfig.test.json and add the following content to it:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./test-dist"
},
"include": [
"src/**/*.test.ts"
]
}
Finally, you can run your tests with npm run jest.
You also need to make sure that you are using import statement correctly in your tests. You should use import statement to import functions, classes, and variables from other modules, but you should use require statement to import modules that are not written in TypeScript.
I have a Vue.js application where two files contain:
import axios from "axios"
These files are located in src/lib within the application and include the import statement on their first line.
Running tests on Github causes Axios 1.0.0 to be installed, no matter what the package.json says, and now any test involving these files fails with the above error.
Changing the statement to const axios = require("axios") fails also; node_modules/axios/index.js contains an import statement on line 1 and the exception is thrown there.
A suggestion I've seen quite often for such issues is to add "type": "module" to package.json (which is at the same level as src/). This causes all tests to fail with a demand to rename vue.config.js as vue.config.cjs. Doing that gets me: Error: You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously, which I do not understand.
Can anyone suggest what to do here?
I was able to fix this error by forcing jest to import the commonjs axios build by adding
"jest": {
"moduleNameMapper": {
"axios": "axios/dist/node/axios.cjs"
}
},
to my package.json. Other solutions using transformIgnorePatterns didn't work for me.
The 1.x.x version of axios changed the module type from CommonJS to ECMAScript.
The 0.x.x version of axios index.js file
module.exports = require('./lib/axios');
The 1.x.x version of axiox index.js file
import axios from './lib/axios.js';
export default axios;
Basically, jest runs on Node.js environment, so it uses modules following the CommonJS.
If you want to use axios up to 1.x.x, you have to transpile the JavaScript module from ECMAScript type to CommonJS type.
Jest ignores /node_modules/ directory to transform basically.
https://jestjs.io/docs/27.x/configuration#transformignorepatterns-arraystring
So you have to override transformIgnorePatterns option.
There are two ways to override transformIgnorePatterns option.
jest.config.js
If your vue project uses jest.config.js file, you add this option.
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
transformIgnorePatterns: ["node_modules/(?!axios)"],
...other options
};
package.json
If your vue project uses package.json file for jest, you add this option.
{
...other options
"jest": {
"preset": "#vue/cli-plugin-unit-jest",
"transformIgnorePatterns": ["node_modules\/(?!axios)"]
}
}
This regex can help you to transform axios module and ignore others under node_modules directory.
https://regexper.com/#node_modules%5C%2F%28%3F!axios%29
Updating the version of jest to v29 fixed this in my project. It could be the case that you have an incompatible jest version.
I had the same issues and was able to solve this by using jest-mock-axios library
I experience similar problem but the error is caused by jest.
All the tests trying to import axios fail and throw the same exception:
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/monorepo/node_modules/axios/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import axios from './lib/axios.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
1 | import { describe, expect, it } from '#jest/globals'
> 2 | import axios from 'axios'
The solution is simply tell jest that axios should be transformed with babel:
const esModules = ['lodash-es', 'axios'].join('|')
# add these entries in module.exports
transform: {
[`^(${esModules}).+\\.js$`]: 'babel-jest',
},
transformIgnorePatterns: [`node_modules/(?!(${esModules}))`],
Note: I'm using Quasar Vue and this is their implementation.
Quick fix
Update the npm run test script from
"test": "react-scripts test",
to
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
In my case I had to add the following line to the moduleNameMapper object in the jest config:
axios: '<rootDir>/node_modules/axios/dist/node/axios.cjs',
I had the same issue, it works fine when changing axios to fetch.
axios (Fail)
try {
const response = await axios("api/fruit/all");
return response.data;
} catch (error) {
return error;
}
Fetch (Works fine)
try {
const response = await fetch("api/fruit/all",{method:"GET"});
const data = await response.json();
return data;
} catch (error) {
return error;
}
This question already has answers here:
Node.js - SyntaxError: Unexpected token import
(16 answers)
Closed 3 years ago.
I'm trying to get the hang of ES6 imports in Node.js and am trying to use the syntax provided in this example:
Cheatsheet Link
I'm looking through the support table, but I was not able to find what version supports the new import statements (I tried looking for the text import/require). I'm currently running Node.js 8.1.2 and also believe that since the cheatsheet is referring to .js files it should work with .js files.
As I run the code (taken from the cheatsheet's first example):
import { square, diag } from 'lib';
I get the error:
SyntaxError: Unexpected token import.
Reference to library I'm trying to import:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
What am I missing and how can I get node to recognize my import statement?
Node.js has included experimental support for ES6 support.
Read more about here: https://nodejs.org/docs/latest-v13.x/api/esm.html#esm_enabling.
TLDR;
Node.js >= v13
It's very simple in Node.js 13 and above. You need to either:
Save the file with .mjs extension, or
Add { "type": "module" } in the nearest package.json.
You only need to do one of the above to be able to use ECMAScript modules.
Node.js <= v12
If you are using Node.js version 9.6 - 12, save the file with ES6 modules with .mjs extension and run it like:
node --experimental-modules my-app.mjs
You can also use npm package called esm which allows you to use ES6 modules in Node.js. It needs no configuration. With esm you will be able to use export/import in your JavaScript files.
Run the following command on your terminal
yarn add esm
or
npm install esm
After that, you need to require this package when starting your server with node. For example if your node server runs index.js file, you would use the command
node -r esm index.js
You can also add it in your package.json file like this
{
"name": "My-app",
"version": "1.0.0",
"description": "Some Hack",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node -r esm index.js"
},
}
Then run this command from the terminal to start your node server
npm start
Check this link for more details.
I just wanted to use the import and export in JavaScript files.
Everyone says it's not possible. But, as of May 2018, it's possible to use above in plain Node.js, without any modules like Babel, etc.
Here is a simple way to do it.
Create the below files, run, and see the output for yourself.
Also don't forget to see Explanation below.
File myfile.mjs
function myFunc() {
console.log("Hello from myFunc")
}
export default myFunc;
File index.mjs
import myFunc from "./myfile.mjs" // Simply using "./myfile" may not work in all resolvers
myFunc();
Run
node --experimental-modules index.mjs
Output
(node:12020) ExperimentalWarning: The ESM module loader is experimental.
Hello from myFunc
Explanation:
Since it is experimental modules, .js files are named .mjs files
While running you will add --experimental-modules to the node index.mjs
While running with experimental modules in the output you will see: "(node:12020) ExperimentalWarning: The ESM module loader is experimental.
"
I have the current release of Node.js, so if I run node --version, it gives me "v10.3.0", though the LTE/stable/recommended version is 8.11.2 LTS.
Someday in the future, you could use .js instead of .mjs, as the features become stable instead of Experimental.
More on experimental features, see: https://nodejs.org/api/esm.html
Using Node.js v12.2.0, I can import all standard modules like this:
import * as Http from 'http'
import * as Fs from 'fs'
import * as Path from 'path'
import * as Readline from 'readline'
import * as Os from 'os'
Versus what I did before:
const
Http = require('http')
,Fs = require('fs')
,Path = require('path')
,Readline = require('readline')
,Os = require('os')
Any module that is an ECMAScript module can be imported without having to use an .mjs extension as long as it has this field in its package.json file:
"type": "module"
So make sure you put such a package.json file in the same folder as the module you're making.
And to import modules not updated with ECMAScript module support, you can do like this:
// Implement the old require function
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
// Now you can require whatever
const
WebSocket = require('ws')
,Mime = require('mime-types')
,Chokidar = require('chokidar')
And of course, do not forget that this is needed to actually run a script using module imports (not needed after v13.2):
node --experimental-modules my-script-that-use-import.js
And that the parent folder needs this package.json file for that script to not complain about the import syntax:
{
"type": "module"
}
If the module you want to use has not been updated to support being imported using the import syntax then you have no other choice than using require (but with my solution above that is not a problem).
I also want to share this piece of code which implements the missing __filename and __dirname constants in modules:
import {fileURLToPath} from 'url'
import {dirname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
If you are using the modules system on the server side, you do not need to use Babel at all. To use modules in Node.js ensure that:
Use a version of node that supports the --experimental-modules flag
Your *.js files must then be renamed to *.mjs
That's it.
However and this is a big however, while your shinny pure ES6 code will run in an environment like Node.js (e.g., 9.5.0) you will still have the craziness of transpilling just to test. Also bear in mind that Ecma has stated that release cycles for JavaScript are going to be faster, with newer features delivered on a more regular basis. Whilst this will be no problems for single environments like Node.js, it's a slightly different proposition for browser environments. What is clear is that testing frameworks have a lot to do in catching up. You will still need to probably transpile for testing frameworks. I'd suggest using Jest.
Also be aware of bundling frameworks. You will be running into problems there.
Use:
"devDependencies": {
"#babel/core": "^7.2.0",
"#babel/preset-env": "^7.2.0",
"#babel/register": "^7.0.0"
}
File .babelrc
{
"presets": ["#babel/preset-env"]
}
Entry point for the Node.js application:
require("#babel/register")({})
// Import the rest of our application.
module.exports = require('./index.js')
See How To Enable ES6 Imports in Node.js
You may try esm.
Here is some introduction: esm
Using the .mjs extension (as suggested in the accepted answer) in order to enable ECMAScript modules works. However, with Node.js v12, you can also enable this feature globally in your package.json file.
The official documentation states:
import statements of .js and extensionless files are treated as ES modules if the nearest parent package.json contains "type": "module".
{
"type": "module",
"main": "./src/index.js"
}
(Of course you still have to provide the flag --experimental-modules when starting your application.)
Back to Jonathan002's original question about
"... what version supports the new ES6 import statements?"
based on the article by Dr. Axel Rauschmayer, there is a plan to have it supported by default (without the experimental command line flag) in Node.js 10.x LTS. According to node.js's release plan as it is on 3/29, 2018, it's likely to become available after Apr 2018, while LTS of it will begin on October 2018.
Solution
https://www.npmjs.com/package/babel-register
// This is to allow ES6 export syntax
// to be properly read and processed by node.js application
require('babel-register')({
presets: [
'env',
],
});
// After that, any line you add below that has typical ES6 export syntax
// will work just fine
const utils = require('../../utils.js');
const availableMixins = require('../../../src/lib/mixins/index.js');
Below is definition of file *mixins/index.js
export { default as FormValidationMixin } from './form-validation'; // eslint-disable-line import/prefer-default-export
That worked just fine inside my Node.js CLI application.
I don't know if this will work for your case, but I am running an Express.js server with this:
nodemon --inspect ./index.js --exec babel-node --presets es2015,stage-2
This gives me the ability to import and use spread operator even though I'm only using Node.js version 8.
You'll need to install babel-cli, babel-preset-es2015, and babel-preset-stage-2 to do what I'm doing.
There is a 3rd party all-javascript npm scoped package, let's call it #foo, with a module inside called bar. I wanted to use the react component #foo/bar/X from within my typescript .tsx file. I immediately ran into "module not found" when I tried to import X from '#foo/bar/X'. How can I resolve this using #types typescript to resolve the module X and get webpack to run without errors?
My starting point was this introduction on how to how to use react and webpack with typescript: https://www.typescriptlang.org/docs/handbook/react-&-webpack.html. However, said instructions do not tackle the problem that the react component I want to use has no #types. So my first step is I needed to add #types.
There was one important nuance about the scoped module #foo/bar, which was that it broke all its subcomponents into individual sub modules like #foo/bar/X, #foo/bar/Y, and #foo/bar/Z with #foo/bar itself having no functionality. As we will see that was an important nuance which made solving this a bit trickier than if I had not had to import a scoped module into my typescript.
There is a nice blog post here about how to import a vanilla javascript into typescript. Unfortunately there is a shortcoming to the proposed approach. Namely, the idea of adding "index.d.ts' to a src/#typings has two problems:
It works only for running the typescript compiler (tsc) locally. That is to say it will make tsc happy, by allowing tsc to resolve the module from your your local ".d.ts" file, but webpack will still fail with module not found.
The proposed approach didn't teach me how to deal with scoped modules which have different rules for how folders under #types are named
Whatever approach I tried, I knew I needed to have this import statement in my typescript file:
import X from '#foo/bar/X';
ReactDOM.render(
<X/>,
document.getElementById("example")
);
Somewhere, I needed to have the following in a ".d.ts" declarations file like index.d.ts, that much was obvious:
declare module '#foo/bar';
declare module '#foo/bar/X';
But running 'webpack' yielding Module not found: Error: Can't resolve '#foo/bar/X'
I decided to run the typescript compiler (tsc) with this flag to see the possible places where tsc would try to resolve the module.
tsc --traceresolution
Using the traceresolution flag I was able to see something very interesting: that the location under node_modules/#types where tsc searches was quite unexepected (and thereby where webpack searches, since webpack follows the same rules as tsc). It was expecting to find my index.d.ts file under node_modules/#types/foo__bar NOTE THE DOUBLE UNDERSCORE. That's right, for scope packages, you cannot have node_modules/#types/#foo/bar/index.d.ts. Instead you must use a single folder under #types named foo__bar with DOUBLE UNDERSCORE.
With this knowledge I created #types/foo__bar. I copied package.json from the #foo/bar module into #types/foo__bar. I cleaned out all the scripts and other useless stuff that the #types would not need, and stripped it down to this:
{
"name": "#types/#sfoo/bar",
"version": "2.6.0",
"peerDependencies": {
"react": "^16.3",
"react-dom": "^16",
"styled-components": "^3"
},
"dependencies": {
...
},
"engines": {
"node": ">=6"
},
"gitHead": "ac0288aaa47a4f15e56db3a5eff4424fb7905419",
"main": "",
"types": "index"
}
But yet there was one more problem! Even after tsc could resolve my #types, webpack still yielded module not found: can't resolve #foo/bar. So why the heck can't webpack find it? Again, I found a flag that could show me more:
webpack --verbose
But here I had less luck since the webpack rules were just like the typescript rules for module resolution. However from the --verbose output I did notice that one file webpack was interested in was node_modules/#foo/bar/index.js. However, the bar module lacked any index.js since it was really just an empty enclosing module around submodules like #foo/bar/X. Whether this is a bug or a feature of webpack, I don't know, but ADDING AN EMPTY index.js to node_modules/#foo/bar pacified webpack, and my simple example now worked with component X displayed in the browser when I loaded the HTML page.
Facebook's Jest testing framework is easy to get started with, but the documentation overlooks an annoying aspect: test statements will be highlighted as errors by any editor that tries to warn of undefined symbols, because test, expect, and all matcher methods are not defined.
Similary, attempting to run a test file with node directly will fail with ReferenceError: test is not defined.
What require/import statement(s) need to be added for those errors to go away?
Node
If you want to run them directly through node, try to require jest and/or jest-runtime. Also give #types/jest a try as well.
Check Edit 2 for new info about this
Edit
#types/jest (jest-DefinitelyTyped) is definitely needed (or just one solution). If you install it (e.g., dev dependency), the IDE errors should go away.
I just tried it on Webstorm, and it works.
Edit 2
The new Jest#20 Matchers (e.g., .resolves and .rejects) are still not defined in #types/jest. You can keep track of its status on the links below:
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/16645
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16803
It should be available soon, though!
Also, it doesn't seem possible to run it directly through node. Last night I tried a bunch of different things, but using jest is the way to go - it really uses node under the hood, so I thought it would be possible as well. #thymikee over your opened issue at GitHub made clear that it's not.
Edit 3
The new release (20.0.1) includes the newest Jest definitions.
Lint
this isn't in the scope of this specific problem, but it also helps
Are you using something like ESLint? If so, you'll need eslint-plugin-jest
Following the steps described in this page: https://www.npmjs.com/package/eslint-plugin-jest, you will basically need to add it as an ESLint plugin and set jest globals in the ESLint configuration:
{
"env": {
"jest/globals": true
}
}
If you plan on supporting ES6 tests, you'll also need Babel and babel-jest plugin with the following jest configuration:
"transform": {
"^.+\\.js$": "babel-jest"
}
Finally, for Typescript tests you'd need the #types/jest and ts-jest packages as well
Adding following .eslintrc configuration is enough
{"env":
{
"jest": true
}
}
I'm using VSCode and ESLint, you need to install eslint-plugin-jest
Add jest info to your .eslintrc.js
{
"plugins": ["jest"]
},
"env": {
"jest/globals": true
}