Running tests with ES6 import statements node - javascript

I'm using node v15.0.1, and jest 26.6.0 on ubuntu 18.04.5.
I have setup a simple test case, and at the top of the file I'm trying to use an ES6 import statement:
import Color from './color.js'
test("Initialized properly after construction", () => {
expect(1 + 1).toBe(2);
});
Additionally, here's the code for color.js:
class Color {
constructor(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
}
export {
Color
};
When I run jest I get the following error output:
FAIL src/modules/color.test.js
● 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:
• 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:
/home/daniel/Documents/raycaster/src/modules/color.test.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import Color from './color.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module
at new Script (node:vm:100:7)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.288 s
Ran all test suites.
npm ERR! code 1
Based on the Jest documentation https://jestjs.io/docs/en/ecmascript-modules, I have added the following to my package.json file:
"type": "module",
"jest": {
"testEnvironment": "jest-environment-node",
"transform": {}
}
Despite these configurations it appears that jest is unable to run in an ES6 compliant mode. What configurations would I need to do to enable the import statements?

I found Node v13 / Jest / ES6 — native support for modules without babel or esm
which highlighted the piece that I needed:
In my package.json file, I needed to specify the following:
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"

As the reference states,
babel-jest is automatically installed when installing Jest and will automatically transform files if a babel configuration exists in your project. To avoid this behavior, you can explicitly reset the transform configuration option:
<...>
transform: {},
This is exactly what this config does, it disables Babel and prevents import from being transpiled.
The solution is to remove transform: {} and use transform only on purpose.
The mentioned reference section is dedicated to native ES module support in Node. It suggests that transform: {} requires to enable them with:
node --experimental-vm-modules node_modules/.bin/jest
This cannot be recommended for regualr use as ESM support in both Node and Jest is experimental, may cause issues and lack features as Jest already heavily relies on CommonJS modules.

Since you are importing the class as a default export, you need to a default value. For more insight check this out https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
class Color {
constructor(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
}
export default Color;

Related

Configure jest with latest version of d3-path

For some reason, my jest configuration doesn't work with the latest version of d3-path#3.0.1. It worked fine with version 2.0.0. I guess it has something to do with d3-path switching to ESM, but I was already using ES6 in my own code, so I don't get why it suddenly doesn't work anymore. I have the following packages installed:
"dependencies": {
"d3-path": "^3.0.1"
},
"devDependencies": {
"#babel/core": "^7.15.8",
"#babel/preset-env": "^7.15.8",
"babel-jest": "^27.3.1",
"jest": "^27.3.1"
}
My babel.config.js:
module.exports = {
presets: [['#babel/preset-env', {targets: {node: 'current'}}]],
};
My index.js:
import { path } from 'd3-path'
export default () => path()
The test file:
import fn from '../src/index.js'
describe('test', () => {
it('works', () => {
fn()
expect(2 + 2).toBe(4)
})
})
The error message:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export {default as path} from "./path.js";
^^^^^^
SyntaxError: Unexpected token 'export'
> 1 | import { path } from 'd3-path'
To reproduce:
git clone https://github.com/luucvanderzee/jest-problem.git
cd jest-problem
npm i
npm run test
// The test runs without failure- this is because we're currently still using d3-path#2.0.0
npm uninstall d3-path && npm install d3-path // (upgrade to d3-path#3.0.1)
npm run test
// Now the test fails.
How should I configure jest and/or babel to solve this issue?
EDIT:
I already tried the following (from this page of the jest docs):
Creating a jest.config.js file with the following:
module.exports = {
transform: {}
}
Changing my "test" command from "jest" to "node --experimental-vm-modules node_modules/jest/bin/jest.js"
This gives me another error:
/home/luuc/Projects/javascript/jest-problem/test/test.test.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import fn from '../src/index.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module
I also don't get what is meant by
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Isn't the problem that the module is not transformed? Would adding an ignore pattern not just lead to the module not getting transformed?
Problem
The error happens because jest does not send the content of node_modules to be transformed by babel by default.
The following output line of npm run test indicates one way to solve the problem:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Solution
The configuration of jest should be updated in order to instruct it to transform the ESM code present in d3-path dependency.
To do so, add the following to a jest.config.js file in the root directory of the project:
module.exports = {
transformIgnorePatterns: ['node_modules/(?!(d3-path)/)']
}
npm run test runs fine after that.
The transformIgnorePatterns option is documented here.
Edit - including more modules
In order to include all modules starting with d3, the following syntax may be used:
transformIgnorePatterns: ['/node_modules/(?!(d3.*)/)']
TLDR;
transformIgnorePatterns: [
"/node_modules/(?!d3|d3-array|d3-axis|d3-brush|d3-chord|d3-color|d3-contour|d3-delaunay|d3-dispatch|d3-drag|d3-dsv|d3-ease|d3-fetch|d3-force|d3-format|d3-geo|d3-hierarchy|d3-interpolate|d3-path|d3-polygon|d3-quadtree|d3-random|d3-scale|d3-scale-chromatic|d3-selection|d3-shape|d3-time|d3-time-format|d3-timer|d3-transition|d3-zoom}|internmap|d3-delaunay|delaunator|robust-predicates)"
]
For the ones reaching this page after updating recharts dependency, here I found the solution, provided by them.

Nodejs with JEST how to import using ES6 and relative path?

I want to avoid this:
const SomeMethod = require('../shared/SomeMethod')
And instead use something more modern like this:
import { SomeMethod } from '/shared'
(under the hood): the /shared directory includes an index file of course, returning the object with the SomeMethod property which is also includes to a file.
As I am using JEST, I need two things to get around: 1 is that the node installed supports ES6 imports and 2 is that JEST will be familiar with relative path - notice that I have used the **/**shared so it means - go to the src directory and start from there.
But how to achieve this?
You can achieve this using babel. According to the documentation of jest, you need to do the following
yarn add --dev babel-jest #babel/core #babel/preset-env
and then create babel.config.js at the root of your project with the following content
module.exports = {
presets: [
[
'#babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
You can look into the documentation for more
Here is a step by step process of the same which is addressing the same problem
In order to use absolute path for Jest add the following line in jest.config.js
module.exports = {
moduleDirectories: ['node_modules', 'src'],
...
};
here, src is considered as the root. You may need to change this one according to your folder name.
For more information you can follow this article

Jest - Jest encountered an unexpected token. Vanilla JS

I'm trying to make a complex zip extractor in JavaScript and I decided that unit testing would be very important. That being said, a friend recommended Jest. I couldn't get any of my tests to work so I made a dumb test that made sure the first value of my JS Enum is 0. Jest, however, fails ever time saying that it encountered an unexpected token
I tried a more complex test and simplified it down to this simple test:
enums.js:
const Format = {
UNKNOWN: 0,
ZIP: 1,
TAR_GZIP: 2,
TAR_BZIP: 3,
};
export default Format
enums.test.js
const {Format} = require("../src/enums.js");
test("bad test", () => {
expect(Format.UNKNOWN).toBe(0);
});
The error that is gives me is this:
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:
• 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:
/home/giovanni/WebstormProjects/extract.js/src/enums.js:7
export default Format;
^^^^^^
SyntaxError: Unexpected token export
> 1 | const Format = require("../src/enums.js");
| ^
2 |
3 | test("bad test", () => {
4 | expect(Format.UNKNOWN).toBe(0);
at ScriptTransformer._transformAndBuildScript (node_modules/#jest/transform/build/ScriptTransformer.js:471:17)
at ScriptTransformer.transform (node_modules/#jest/transform/build/ScriptTransformer.js:513:25)
at Object.<anonymous> (test/enum.test.js:1:1)
Using Bable fixed this. Once I installed Babel with NPM, I added a file .babelrc with the contents:
{
"presets": ["#babel/preset-env"]
}
This worked.

Dynamic-Imports via babel-register resulting in surplus "default module"

I have (polymorphic) code that is bundled using webpack including dynamic-imports (for code-splitting) and want to run the same code in NodeJS, currently using babel-register to be able to run the ES6 code.
I've encountered an issue when it comes to the dynamic imports: The resolved/loaded module seems to be wrapped in an additional (default-)module.
The following is a minimal example, using NodeJS v10.14.1, #babel/core#7.2.2, #babel/plugin-syntax-dynamic-import#7.2.0, #babel/preset-env#7.3.1, #babel/register#7.0.0:
index.js:
require('#babel/register')({
presets: ['#babel/preset-env'],
plugins: [
'#babel/plugin-syntax-dynamic-import'
]
})
require('./main').run()
dep.js:
export const bar = 3;
main.js:
import * as syncMod from './dep'
export function run() {
console.log(syncMod)
import('./dep').then(mod => console.log(mod))
}
Running this via node --experimental-modules index.js yields:
{ bar: 3 } // the syncMod
[Module] { default: { bar: 3 } } // the dynamically loaded
The "normal" import works as expected and directly gives the object with the exports. The dynamic-import does this too (as I would expect) in the bundled browser-version, but returns this [Module] { default: .. } thing around it in NodeJS.
Running without the babel-register code and rather using babel-node (#babel/node#7.2.2) (npx babel-node --experimental-modules --plugins=#babel/plugin-syntax-dynamic-import --presets=#babel/preset-env index) yields the same result.
I need to run it with the experimental flag, otherwise the dynamic import did not work at all (Not supported Error thrown).
I could access the dynamically-loaded module without further problems by accessing it below the added layer like mod.default.bar, but then my code would differ for browser and NodeJS.
I'm grateful for any explanation for this behaviour and maybe a solution (/where I went wrong) to get the normal/expected exports object.
I'm currently using this helper as a workaround:
function lazyImport(promise) {
if (typeof window !== 'undefined') {
// Browser
return promise
}
// Node
return promise.then(mod => mod.default)
}
and wrap every import() like this:
lazyImport(import('./path/to/mod')).then(mod => /* use the mod */)

Is it possible to use ES6 modules in Mocha tests?

ES6, Windows 10 x64, Node.js 8.6.0, Mocha 3.5.3
Is it possible to use ES6 modules in Mocha tests? I have the problems with export and import keywords.
/* eventEmitter.js
*/
/* Event emitter. */
export default class EventEmitter{
constructor(){
const subscriptions = new Map();
Object.defineProperty(this, 'subscriptions', {
enumerable: false,
configurable: false,
get: function(){
return subscriptions;
}
});
}
/* Add the event listener.
* #eventName - the event name.
* #listener - the listener.
*/
addListener(eventName, listener){
if(!eventName || !listener) return false;
else{
if(this.subscriptions.has(eventName)){
const arr = this.subscriptions.get(eventName);
arr.push(listener);
}
else{
const arr = [listener];
this.subscriptions.set(eventName, arr);
}
return true;
}
}
/* Delete the event listener.
* #eventName - the event name.
* #listener - the listener.
*/
deleteListener(eventName, listener){
if(!eventName || !listener) return false;
else{
if(this.subscriptions.has(eventName)){
const arr = this.subscriptions.get(eventName);
let index = arr.indexOf(listener);
if(index >= 0){
arr.splice(index, 1);
return true;
}
else{
return false;
}
}
else{
return false;
}
}
}
/* Emit the event.
* #eventName - the event name.
* #info - the event argument.
*/
emit(eventName, info){
if(!eventName || !this.subscriptions.has(eventName)) {
return false;
}
else{
for(let fn of this.subscriptions.get(eventName)){
if(fn) fn(info);
}
return true;
}
}
}
Mocha test:
/* test.js
* Mocha tests.
*/
import EventEmitter from '../../src/js/eventEmitter.js';
const assert = require('assert');
describe('EventEmitter', function() {
describe('#constructor()', function() {
it('should work.', function() {
const em = new EventEmitter();
assert.equal(true, Boolean(em));
});
});
});
I launch the mocha directly through the PowerShell console. The result:
Mocha has support for ESM from version 7.1.0 onward (release: Feb. 26, 2020).
This requires Node 12.11.0 or higher, and is subject to the current restrictions/limitations of using modules in Node:
Either you must use .mjs file extensions for source files that use ES modules, or you must have "type": "module" in your package.json
You can't use named imports when importing from CommonJS modules
Local import statements have to explicitly include the .js file extension
And so on.
Updated answer
I had previously recommended using the esm package as an alternative to Mocha's built-in module support, but it is no longer being mantained, can't handle newer syntactical constructs like ?., and seems to possibly not work at all with newer versions of Mocha.
However, #babel/register seems to work well for this:
mocha -r #babel/register -r regenerator-runtime/runtime
I'm using this with this preset (in .babelrc):
{
"presets": [
"#babel/preset-env"
]
}
This setup requires the following packages:
#babel/core
#babel/register
#babel/preset-env
regenerator-runtime
You can also specify these in your .mocharc.js file instead of on the command line:
module.exports = {
require: [
'#babel/register',
'regenerator-runtime/runtime',
],
};
My personal experience as of yet is that trying to take advantage of Mocha's new, inherent ESM support is still a considerable burden, but using this approach is quite seamless.
Previous answer
Another option is to use the esm package, which is not subject to the above limitations:
mocha -r esm
My personal experience as of yet is that trying to take advantage of Mocha's new, inherent ESM support is still a considerable burden, but using the esm package is quite seamless.
In my case run with:
basic comand:
npx mocha --require esm test_path/
package.json
"scripts": {
// ...
"test": "npx mocha --require esm --reporter spec test_path/"
}
Runing
npm test
It's possible with Babel and Browserfy
https://drublic.de/blog/es6-modules-using-browserify-mocha/
Regarding your main question
Is it possible to use ES6 modules in Mocha tests?
Yes, as of Mocha version 9:
Mocha is going ESM-first! This means that it will now use ESM import(test_file) to load the test files, instead of the CommonJS require(test_file). This is not a problem, as import can also load most files that require does. In the rare cases where this fails, it will fallback to require(...). This ESM-first approach is the next step in Mocha's ESM migration, and allows ESM loaders to load and transform the test file.
You also need to use a Node version which supports import, which would be >= 13.2.0
Regarding the Unexpected token import problem - others here wrote good answers, but here's a better answer from another related question:
How does mocha / babel transpile my test code on the fly?

Categories

Resources