How to write unit tests supporting Javascript 6 modules - javascript

My Javascript codebase is based on new ES6 Modules.
So I have Javascript files like this for example:
export class MyClass {
constructor() {
this.list = [];
}
add(el) { this.list.push(el); }
}
As a module, I import this file in other Javascript files like this:
import * as lists from "./myclass";
And inside an HTML page, the following syntax has to be used:
<script src="myclass.js" type="module"></script>
Unit testing
I need a framework for testing my code. The problem is that I am using Javascript 6 modules, so modern frameworks like karma have problems as they import the files not as modules:
module.exports = function(config) {
config.set({
files: [
'src/**/*.js',
'test/**/*.js'
],
...
})
}
Above is an example of karma.conf.js. In the specific case of Karma, the runner will not import the files as modules, thus the injection in page fails.
What unit test frameworks can I use for testing Javascript 6 modules?

I'm using a combination of Mocha and Babel - Babel transpiles the ES6 modules to code Mocha can work with, and so you can use import in the test files.
To run mocha with the Babel transpiler:
mocha --compilers js:babel-core/register --recursive test/*
I'm pretty sure other frameworks have a similar solution.

You can check out Jest, it's Facebook's test framework that allows you to run your tests on Node (with JSDOM).
It runs your tests in parallel and without browser, therefore suppose to be much faster.

I'm not sure Jest actually supports modules, right? I.e. I want my test files .. those with the assertions .. to be able to import modules which also import other modules.
It seems the best you can do with node based testing frameworks, with I think may be all of them, is us pretty awful babel/webpack stunts which I'd really prefer not to use.
Headless Chrome certainly seems to be interesting, as does Puppeteer
https://github.com/GoogleChrome/puppeteer
https://developers.google.com/web/updates/2017/06/headless-karma-mocha-chai

Related

How can I import ES6 class modules into Jasmine for testing?

I want to unit test some ES6 classes that are stored as modules. However, when I try to run my tests, import triggers an error message: Cannot use import statement outside a module which means I can't test my modules.
All the Jasmine examples use the old ES5 syntax with classes defined as functions using the modules.export and then imported using the require function. I've found websites such as this where they seem to use the ES6 and import { module } from 'path' syntax, but I have no idea why this works and why mine doesn't? Is it because I'm not testing using a headless browser? It is because Jasmine doesn't support this functionality natively and I need some other package? Is it because I need to use jasmine-node in some specific way to get it to work? Should I not be using ES6 classes at all?
As its probably obvious I'm a beginner to node and javascript but I wouldn't have thought that just testing the functionality of a class would be this difficult so if anyone could give me some guidance on how to accomplish testing a class module in Jasmine I would be really grateful.
For example I have a module.
// src/myClass.mjs
export class MyClass {
constructor() { }
}
I have a simple Jasmine unit test.
// spec/myClassSpec.js
import { MyClass } from '../src/myClass.mjs'
describe('my class', function() {
var myClassInstance
beforeEach(function() {
myClassInstance = new MyClass()
})
it('is an instance of MyClass', function() {
expect(myClassInstance).toBeInstanceOf(MyClass)
})
})
I may be late to answer however using "import" statement in Nodejs is not as simple as it looks.
There are few main differences between require and import (like being async) thus one can not be simply converted into another.
**
A simple solution is to use rollup
**.
Once you have completed your module your can use rollup to bundle this module as a commonjs file. That file can then be used for testing.
To install rollup
npm install rollup --save-dev
After rollup in installed you need to add following commands to the script of "package.json" file
"dist": "rollup -c"
You need to have a rollup.config.js file in the root folder of your application.
The content of rollup.config.js are:
import { rollup } from "rollup";
export default {
input: './index.js',
output: {
file: 'dist/index.js',
format: 'cjs' //cjs stands for common js file
}
};

how code splitting works with import/export and babel and webpack?

I am trying to answer,
when to use import/export and when to use require()/module.exports? But as I try to dig, it seems to get complicated.
Here's my understanding
require()/module.exports: this is nodejs implementation of the module system. This loads the modules syncronously.
with es6, we can use import/export. the docs says
The import statement is used to import bindings which are exported by another module. Imported modules are in strict mode whether you declare them as such or not. The import statement cannot be used in embedded scripts unless such script has a type="module".
Ques1: How does this work with babel or webpack or browsers in general?
As I was exploring I came across stuff like CommonJs, requireJs, Asynchronous Module Definition (AMD)
Ques2: I am more interested in knowing the timeline as how these things evolved in javascript ?
How does this work with babel or webpack or browsers in general?
Babel and Webpack follow the ES spec and transpile the import / export statement to one single file. As they also support the require syntax, they usually transpile the import statements to require() calls and the export statements to module exports, and then ship with a custom loader for modules., If you got for example:
// A.js
export default function() { }
// B.js
import A from "./A";
A();
Then it gets transpiled to the following require syntax:
//A.js
exports.default = function() {};
//B.js
var A = require("./A").default;
A();
That could then get wrapped to something like:
(function() { // prevent leaking to global scope
// emulated loader:
var modules = {};
function require(name) { return modules[name]; }
function define(name, fn) {
var module = modules[name] = { exports: {} };
fn(module, module.exports, require);
}
// The code:
define("A", function(module, exports, require) {
// A.js
exports.default = function() { };
});
define("B", function(module, exports, require) {
// B.js
var A = require("A").default;
A();
});
})();
how these things evolved in javascript ?
A few years ago, writing JS was restricted to browsers, and the only way to load multiple js sources was to use multiple <script> tags and use the global object to exchange functionality. That was ugly.
Then Nodejs was invented and they needed a better way to work with modules and invented the require() thing.
The writers of the spec saw a need for a native syntax for that, so import / export were introduced.
Babel and others then wrote transpilers.
What webpack the bundler does is the following:
You specify an input file in the config
You specify an output file the config
Webpack will look at all the files which the input file requires (commomJS module system) or imports (ES6 module system). It then funnels the code based on file name extention through loaders. Loaders can transpile the individual files to code the browser can understand. An example of a loader is babel or the sass/scss compiler.
After the different files are transpiled with loaders, the plugins can work at the
transform the bundle of generated code into something else. The bundle is just a bunch of code which together forms piece of functionality
In won't go into detail in the internals of webpack too deeply, but the most important thing to understand is:
You use webpack so you can use split up your code in multiple files, which makes them more maintainable and easier to work with. However then requesting all these files by the client would be horrible for performance (many HTTP requests overhead). Therefore, we bundle the files into one file, or a couple so this overhead is reduced.
Generally, you should write all modern code with import/export syntax if you are using a bundler like webpack, or translating with Babel... npm modules may favor require/module syntax but you can still import them.
Also worth noting is the import() method which returns a promise that should resolve to the root module being imported asynchronously. Webpack may bundle these as asynchronous modules if you have it configured to do so.
In practice the resolution through tooling like babel and webpack will fallback to node-style behaviors against the node_modules lookup, where the standard is transport paths, favoring relative paths. Additional support is per environment.
You can experiment with esm support in modern browsers and in current node (behind a flag as of this answer). The behaviors are somewhat inconsistent, but well defined. When in doubt, experiment and try.

Run tests against compiled bundles

As a JS library author I compile and publish my source code in the following formats:
commonJS (/lib/index.js)
ES (/es/index.js)
UMD (/dist/index.js)
My unit tests cover my source code and I trust my bundling/compiling tools to generate working bundles.
Then I read this enlightening post by React team where they explain that they run part of their unit tests against the bundled version of the library.
They introduced a test-build-prod which runs Jest with a special configuration file which replace the original import declarations in the test to point the bundled files using Jest's moduleNameMapper option.
It's cool but a bit overwhelming to replicate in my tiny open source side projects.
Before I give this a try, is there any other tool or more portable solution I should consider to run the same test I run on my source code against the compiled bundles?
I'll share the solution I finally went with, which is the same adopted by React team but on small scale.
I added a special npm script to run my unit tests against each compiled bundle with a different Jest configuration for each bundle:
{
"test:bundles": "jest --config ./jest/es.config.js && jest --config ./jest/lib.config.js && jest --config ./jest/dist.config.js"
}
Each Jest configuration file extends default Jest configuration and declares a moduleNameMapper object plus a rootDir property like:
// jest/lib.config.js
const pkg = require('../package.json');
module.exports = Object.assign({}, pkg.jest, {
rootDir: '../',
moduleNameMapper: {
'/src/index$': '<rootDir>/lib/index', // path of "CommonJS" bundle
},
});
moduleNameMapper will make sure that the same tests used for source code will run against an exported bundle since Jest will transform the import statements on runtime like:
import myLibrary from './src/index';
// transformed into:
import myLibrary from '../lib/index';
The only point to pay attention to is making tests import statements needing remapping easily recognizable by moduleNameMapper regex.
If import from './index' is not unique enough in test the files, it might be rewritten as import from '../src/index'.

Writing Unit Tests for TSX (TypeScript for JSX/React) for the Browser

I have been researching a good workflow for developing React based components in TypeScript.
Since 1.6, TypeScript has native support for JSX in TypeScript files through the .tsx extension and with tsd we can get IDE (atom/WebStorm) to support type checking.
However, there does not appear to be an easy way to test React components written in tsx.
Can you share your insights on this?
I am considering (almost obliged to use) Jest as opposed to pure Jasmine because Jest's runtime environment comes with a DOM. However, jest appears to only work with ES2015 through babel and es2015, react presets. Is there a readily available way for Jest to work with tsx?
Currently, I can test React components written in ES2015 like:
// SampleComponent.js
import React from 'react';
class SampleComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (<div className='container'>Hello World!</div>);
}
}
module.exports = SampleComponent;
and the corresponding test might look like:
// __tests__/SampleComponent-test.js
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
const SampleComponent = require('../SampleComponent');
describe('SampleComponent', ()=>{
it("has some text", ()=>{
const result = TestUtils.renderIntoDocument(
<SampleComponent />
);
const sampleComponentNode = ReactDOM.findDOMNode(result);
// expect statements
});
});
this works fine, but if we replace the .js extension with .tsx, and change the scriptPreprocessor to be:
'use strict';
var ts = require('typescript');
module.exports = {
process: function(src, path) {
if (path.match(/\.(ts|tsx)$/) && !path.match(/\.d\.ts$/)) {
return ts.transpile(src, {jsx: ts.JsxEmit.React, module: ts.ModuleKind.CommonJS});
}
return src;
},
};
All hell breaks loose, especially with regard to the way TypeScript handle module loading
After exploring, I've come to a tentative solution that works well especially if your project integrates react with other frameworks.
This solution does need elaborate module loading boilerplate (which I dread for front-only projects). I think this approach will last until ES6 module loading becomes standard.
Tentative Solution
Test Runner
karma
Browser
phantomjs (so it can be readily available in CI servers)
Engine
jasmine
Transpiler
tsc with --jsx react or {"jsx": "react"} in tsconfig.json
To Test with Libraries (e.g. angular-mocks)
Load them in karma2.conf.js like so:
// list of files / patterns to load in the browser
files: [
// bind-polyfill helps phantomjs acquire the Function.prototype.bind function
'./node_modules/phantomjs-polyfill/bind-polyfill.js',
"bower_components/jquery/dist/jquery.js",
"bower_components/angular/angular.js",
"bower_components/react/react-with-addons.js",
"bower_components/react/react-dom.js",
"bower_components/angular-mocks/angular-mocks.js",
'src/**/*.js'
],
Compile All TS files to ES5, Do Not export Symbols
By not exporting symbols from .ts and .tsx files, the compiled js files can be simply loaded through karma2.conf.js as seen above.
Why phantomjs?
It has a DOM and allows you to render and manipulate react components.
Facebook's own testing framework: Jest provide the same benefits (as some others).
Downside
Since karma uses a browser and files are loaded in ways similar to a traditional web site, it might be difficult loading code mean't for the backend (i.e. not available through Bower). For example, instead of using react TestUtils I was forced to use jQuery + real DOM provided by Phantom.
Production
As Louy mentioned in the comments, webpack or any one of the standard task runners could be used

How to test multiple builds of the same module?

I want to publish a module to several component manager systems: npmjs, bower, etc... plus I want to create downloadable builds as well, for example one in AMD style for requirejs, one in commonJS style, one for the global namespace in browser, minified for each of them, etc... These are more than 10 builds.
I currently have a single AMD build, and I wrote unit tests for it using karma, jasmine and requirejs in an amd style. What do you suggest, how to generate the other builds and the tests for them?
I mean I cannot decide what should I have as a base of transformations. There is a common part in every output package, and there is a package dependent part either.
AMD - requirejs (I am not sure about using the config options)
define(["module", "dependency"], function (module, dependency) {
var m = {
config: function (options){
//...
},
//...
//do something with the dependency
};
m.config(module.config()); //load config options set by require.config()
return m;
});
commonJS
var dependency = require("dependency");
module.exports = {
config: function (options){
//...
},
//...
//do something with the dependency
};
global
var m = (function (){
return {
config: function (options){
//...
},
//...
//do something with the dependency
};
})(dependency);
I don't know, should I develop the common code and build before every test, or should I develop one of the packages, test it, and write a transformation from that into the other builds?
I intend to use gulp for creating the builds and call unit tests automatically for each of them before automatically publishing them. Ohh and ofc I need an auto version number change as well. Btw. is it necessary to call unit tests after the building procedure, what do you think? I just want to be sure, that not a buggy code is published...
There are transformation libraries for gulp:
https://github.com/phated/gulp-wrap-amd (commonjs to amd)
https://github.com/gfranko/amdclean (amd to standard js)
https://github.com/phated/gulp-wrap-umd (commonjs to umd I guess)
https://github.com/adamayres/gulp-wrap (not sure of its capabilities yet, maybe universal with custom templates, maybe nothing)
So it is easy to transform one package format to the two other formats. This can be done with the tests and the code as well. The commonjs can be tested with node and jasmine-node, the standard js can be tested with karma and jasmine, the amd can be tested with karma, requirejs and jasmine.
It would be a poor choice to create a common descriptor and convert that before every test. I don't want to create another language, like coffeescript, etc... so conversion between packages is okay.
Calling unit tests before publishing is okay. There are no other common package types, so this 3 packages will be enough for every component manager I will use.
I am unsure about the versioning. It is not hard to set it manually, but maybe a system like travis or maven could help more... I'll figure out and edit this answer.

Categories