Is it possible to use ES6 modules in Mocha tests? - javascript

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?

Related

Running tests with ES6 import statements node

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;

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 */)

How to tell which files are being transpiled by Babel 6?

I have a project that is using babel-register to dynamically transpile ES6 source to ES5 when requiring that module in a Node 6.6 project. I've read that babel-register hooks into Node's require function in order to transpile a file when you try to load it, but I'm not always clear on which files will be affected by that change.
This question comes up for me a lot when I'm writing tests: is only my production code getting transpiled, or does the test code get transpiled too?This brings me to the more general question, which is the topic of this post:
How can I tell when Babel is actually running, and which files are being transpiled?
Example code
Let's say I have production classes like this that are written in ES6 syntax
//src/greeter.js
export default class Greeter {
sayHello() {
return 'Hello World';
}
}
and Babel is configured to transpile as so (.babelrc)
{
"presets": ["es2015"]
}
and then there's some test code
//features/step_definitions/greeter_steps.js
import Greeter from '../../src/greeter'; //Causes greeter.js to be transpiled
import expect from 'expect';
var stepWrapper = function() {
//Does Babel try to transpile this code too?
this.Given(/^a greeter$/, function() {
this.greeter = new Greeter();
});
this.When(/^I ask it for a general greeting$/, function() {
this.greeting = this.greeter.sayHello();
});
this.Then(/^it should greet the entire world$/, function() {
expect(this.greeting).toEqual('Hello World');
});
};
module.exports = stepWrapper;
and all of that runs on Node like so
cucumberjs --compiler js:babel-core/register
Example code is available here, if that is helpful.
I made a hack to node_modules/babel-register/lib/node.js to do some logging like so
function compile(filename) {
var result = void 0;
var opts = new _babelCore.OptionManager().init((0, _extend2.default)({ sourceRoot: _path2.default.dirname(filename) }, (0, _cloneDeep2.default)(transformOpts), { filename: filename }));
var cacheKey = (0, _stringify2.default)(opts) + ":" + babel.version;
var env = process.env.BABEL_ENV || process.env.NODE_ENV;
console.log('[babel-register::compile] filename=' + filename + '\n'); //Added logging here
if (env) cacheKey += ":" + env;
if (cache) {
var cached = cache[cacheKey];
if (cached && cached.mtime === mtime(filename)) {
result = cached;
}
}
...
}
which then reports that test and production code are at least passing through Babel on some level
$ npm t
> cucumber-js-babel#1.0.0 test /Users/krull/git/sandbox/node/cucumber-js-babel
> cucumberjs --compiler js:babel-core/register
[babel-register::compile] filename=.../node/cucumber-js-babel/features/step_definitions/greeter_steps.js
[babel-register::compile] filename=.../node/cucumber-js-babel/src/greeter.js
...test results...
However, I'm hoping for a better solution that
works by some means of plugins and/or configuration, instead of monkey patching
better distinguishes which files are actually being transpiled, and which ones pass through Babel without modification
Because of this:
cucumberjs --compiler js:babel-core/register
...babel is invoked for both your test and regular source code. Keep in mind that in node, the only way to import JS is through require, so obviously babel-register will always be invoked. Of course, what babel does depends on its configuration, but most likely you have a simple configuration where all files required by require except those under node_modules will be transpiled.

Packaging-up Browser/Server CommonJS modules with dependancies

Lets say I'm writing a module in JavaScript which can be used on both the browser and the server (with Node). Lets call it Module. And lets say that that Module would benefit from methods in another module called Dependancy. Both of these modules have been written to be used by both the browser and the server, à la CommonJS style:
module.js
if (typeof module !== 'undefined' && module.exports)
module.exports = Module; /* server */
else
this.Module = Module; /* browser */
dependancy.js
if (typeof module !== 'undefined' && module.exports)
module.exports = Dependancy; /* server */
else
this.Dependancy = Dependancy; /* browser */
Obviously, Dependancy can be used straight-out-of-the-box in a browser. But if Module contains a var dependancy = require('dependency'); directive in it, it becomes more of a hassle to 'maintain' the module.
I know that I could perform a global check for Dependancy within Module, like this:
var dependancy = this.Dependancy || require('dependancy');
But that means my Module has two added requirements for browser installation:
the user must include the dependency.js file as a <script> in their document
and the user must make sure this script is loaded before module.js
Adding those two requirements throws the idea of an easy-going modular framework like CommonJS.
The other option for me is that I include a second, compiled script in my Module package with the dependency.js bundled using browserify. I then instruct users who are using the script in the browser to include this script, while server-side users use the un-bundled entry script outlined in the package.json. This is preferable to the first way, but it requires a pre-compilation process which I would have to run every time I changed the library (for example, before uploading to GitHub).
Is there any other way of doing this that I haven't thought of?
The two answers currently given are both very useful, and have helped me to arrive at my current solution. But, as per my comments, they don't quite satisfy my particular requirements of both portability vs ease-of-use (both for the client and the module maintainer).
What I found, in the end, was a particular flag in the browserify command line interface that can bundle the modules and expose them as global variables AND be used within RequireJS (if needed). Browserify (and others) call this Universal Module Definition (UMD). Some more about that here.
By passing the --standalone flag in a browserify command, I can set my module up for UMD easily.
So...
Here's the package.js for Module:
{
"name": "module",
"version": "0.0.1",
"description": "My module that requires another module (dependancy)",
"main": "index.js",
"scripts": {
"bundle": "browserify --standalone module index.js > module.js"
},
"author": "shennan",
"devDependencies": {
"dependancy": "*",
"browserify": "*"
}
}
So, when at the root of my module, I can run this in the command line:
$ npm run-script bundle
Which bundles up the dependancies into one file, and exposes them as per the UMD methodology. This means I can bootstrap the module in three different ways:
NodeJS
var Module = require('module');
/* use Module */
Browser Vanilla
<script src="module.js"></script>
<script>
var Module = module;
/* use Module */
</script>
Browser with RequireJS
<script src="require.js"></script>
<script>
requirejs(['module.js'], function (Module) {
/* use Module */
});
</script>
Thanks again for everybody's input. All of the answers are valid and I encourage everyone to try them all as different use-cases will require different solutions.
Of course you could use the same module with dependency on both sides. You just need to specify it better. This is the way I use:
(function (name, definition){
if (typeof define === 'function'){ // AMD
define(definition);
} else if (typeof module !== 'undefined' && module.exports) { // Node.js
module.exports = definition();
} else { // Browser
var theModule = definition(), global = this, old = global[name];
theModule.noConflict = function () {
global[name] = old;
return theModule;
};
global[name] = theModule;
}
})('Dependency', function () {
// return the module's API
return {
'key': 'value'
};
});
This is just a very basic sample - you can return function, instantiate function or do whatever you like. In my case I'm returning an object.
Now let's say this is the Dependency class. Your Module class should look pretty much the same, but it should have a dependency to Dependency like:
function (require, exports, module) {
var dependency = require('Dependency');
}
In RequireJS this is called Simplified CommonJS Wrapper: http://requirejs.org/docs/api.html#cjsmodule
Because there is a require statement at the beginning of your code, it will be matched as a dependency and therefore it will either be lazy loaded or if you optimize it - marked as a dependency early on (it will convert define(definition) to define(['Dependency'], definition) automatically).
The only problem here is to keep the same path to the files. Keep in mind that nested requires (if-else) won't work in Require (read the docs), so I had to do something like:
var dependency;
try {
dependency = require('./Dependency'); // node module in the same folder
} catch(err) { // it's not node
try {
dependency = require('Dependency'); // requirejs
} catch(err) { }
}
This worked perfectly for me. It's a bit tricky with all those paths, but at the end of the day, you get your two separate modules in different files which can be used on both ends without any kind of checks or hacks - they have all their dependencies are work like a charm :)
Take a look at webpack bundler.
You can write module and export it via module exports. Then You can in server use that where you have module.export and for browser build with webpack. Configuration file usage would be the best option
module.exports = {
entry: "./myModule",
output: {
path: "dist",
filename: "myModule.js",
library: "myModule",
libraryTarget: "var"
}
};
This will take myModule and export it to myModule.js file. Inside module will be assigned to var (libraryTarget flag) named myModule (library flag).
It can be exported as commonJS module, war, this, function
Since bundling is node script, this flag values can be grammatically set.
Take a look at externals flag. It is used if you want to have special behavior for some dependencies. For example you are creating react component and in your module you want to require it but not when you are bundling for web because it already is there.
Hope it is what you are looking for.

Handle WebPack CSS imports when testing with Mocha and Babel

When testing .js files that have Webpack CSS imports like import './style.css', Mocha throws a syntax error (because it tries to import and parse the CSS file as JS). There is a solution for this that has already been posted on Stack Overflow, but it only addresses if you aren't already using a compiler with Mocha. I'm using Babel 5. I've tried the following, but it seems that Mocha doesn't support passing multiple compilers:
// npm test script
mocha ./src/**/*Test.js --compilers css:./scripts/mocha-webpack-compiler.js js:babel/register
// scripts/mocha-webpack-compiler.js
function noop() {
return null;
}
require.extensions['.css'] = noop;
Is there a way to have multiple Mocha compilers, or a better way to tell Mocha not to try to parse Webpack CSS imports?
EDIT:
I like the proposed solution by #Giles B below; it was exactly what I needed. However, since I am still on Babel 5 I needed a few tweaks as shown below:
mocha.opts
--require scripts/support/babelhook
--require scripts/support/mocha-webpack-compiler
scripts/babelhook.js
require('babel/register');
scripts/mocha-webpack-compiler.js
function noop() {
return null;
}
require.extensions['.css'] = noop;
mocha script
mocha ./src/**/*Test.js
This is working for me using babel and babel-core, both version 5.8.23.
I came across the same problem and just got it working, so in my mocha.opts file I added the following require calls:
--require test/babelhook
--require test/css-null-compiler
In babelhook.js I have one require statement to load babel:
// This file is required in mocha.opts
// The only purpose of this file is to ensure
// the babel transpiler is activated prior to any
// test code, and using the same babel options
require("babel-register")();
Then from the link you provided I setup css-null-compiler.js as follows:
// Prevent Mocha from compiling class
function noop() {
return null;
}
require.extensions['.css'] = noop;
require.extensions['.scss'] = noop;
Hope that helps.
Based on #Giles' answer above, this is what I used to get working on Babel 6
package.json
"scripts": {
"test": "mocha --compilers js:babel-core/register
--require ./tools/testHelper.js 'src/**/*-spec.#(js|jsx)'",
tools/testHelper.js
// Prevent mocha from interpreting CSS #import files
function noop() {
return null;
}
require.extensions['.css'] = noop;
This enables you to have your tests inside your src folder alongside your components.

Categories

Resources