Supported technologies: Webpack

Wallaby.js provides two different methods to configure projects that use webpack: Wallaby.js built-in postprocessor, and via the open source wallaby-webpack postprocessor.

This documentation page describes the wallaby-webpack postprocessor, if you’re interested in how to use the built-in postprocessor please find its documentation here.

Installation

npm install wallaby-webpack --save-dev

Usage

// Wallaby.js configuration

var wallabyWebpack = require('wallaby-webpack');
var wallabyPostprocessor = wallabyWebpack({
    // webpack options, such as
    // module: {
    //   loaders: [...] or rules: [...]
    // },
    // externals: { jquery: "jQuery" }
  }
);

module.exports = function (wallaby) {
  return {
    // set `load: false` to all source files and tests processed by webpack
    // (except external files),
    // as they should not be loaded in browser,
    // their wrapped versions will be loaded instead
    files: [
      // {pattern: 'lib/jquery.js', instrument: false},
      {pattern: 'src/**/*.js', load: false}
    ],

    tests: [
      {pattern: 'test/**/*Spec.js', load: false}
    ],

    postprocessor: wallabyPostprocessor,

    setup: function () {
      // required to trigger test loading
      window.__moduleBundler.loadTests();
    }
  };
};

You may find a working sample of wallaby.js configuration for webpack in this repository.

webpack

Webpack config

You may also consider re-using your existing webpack config by requiring it in wallaby config and adjusting it the way you need.

var webpackConfig = require('./webpack.config');

// Adjust the config as required
// webpackConfig.plugins.push(...);

var wallabyPostprocessor = wallabyWebpack(webpackConfig);

Webpack options

To make your tests run as fast as possible, specify only the options you need for your tests to run – avoid doing anything that would make each test run slower. Your production Webpack configuration and your test Webpack configuration serve different purpose and don’t have to be absolutely identical.

You don’t need to specify any output options because wallaby-webpack doesn’t use concatenated bundle. While concatenating files is beneficial for a production environment, in a testing environment it is different. Serving a large bundle every time one of many files (that the bundle consists of) changes is wasteful. So instead, each compiled module code is passed to wallaby, wallaby caches it in memory (and when required, writes it to disk) and serves each requested module file separately to properly leverage browser caching.

Below you may find a few optimisation techniques that may cut a few seconds off your webpack build. Most of the techniques are not wallaby.js specific and may be used for other test runners and even your production build webpack config.

NormalModuleReplacement Plugin

When possible, consider using NormalModuleReplacementPlugin with node-noop for dependencies that you don’t test. In this case, you can remove all files that you use the plugin for from your files list so they don’t get copied over to the wallaby cache, don’t get tracked for changes, etc.

For example, if you require style files or images from your source files, but not asserting anything about them, then you may use the NormalModuleReplacementPlugin plugin:

module.exports = function (wallaby) {
  var wallabyWebpack = require('wallaby-webpack');
  var wallabyPostprocessor = wallabyWebpack({
    ...
      plugins: [
        new webpack.NormalModuleReplacementPlugin(/\.(gif|png|scss|css)$/, 'node-noop')
      ]
    ...
  });
  return {
    files: [
      // now there's no need to list .gif, .png, .scss, .css, files here
      ...
    ],
    ...
  }
};

Don’t forget to install the node-noop module by running:

npm install node-noop --save-dev

Null loader

When not possible to use NormalModuleReplacementPlugin, consider using null loader for dependencies that you don’t test, for example for styles or templates that are not used in your tests.

var webpackConfig = {
    module: {
        rules: [
            ...
            {test: /\.(scss|css)$/, use: 'null'}
        ]
    }
};

Externals

When possible, use the webpack externals option to let webpack know not to bundle libraries and instead use their pre-built versions. For example, if you use:

const React = require('react');

in your code and tests, it makes sense to configure wallaby to load the pre-built version of react from node_modules/react/dist and configure webpack to use the external version (as opposed to compile it from sources):

module.exports = function (wallaby) {
  var wallabyWebpack = require('wallaby-webpack');
  var wallabyPostprocessor = wallabyWebpack({
    externals: {
      // Use external version of React
      "react": "React"
    }
    ...
  });
  return {
    files: [
      ...
      {pattern: 'node_modules/react/dist/react-with-addons.js', instrument: false},
      ...
    ],
    ...
  }
};

Source maps and devtool option

Please note that some webpack loaders such as babel-loader require you to use the devtool option in order to generate a source map that is required in wallaby.js for the correct error stack mappings. Wallaby supported devtool values are: source-map, hidden-source-map, and cheap-module-source-map.

var webpackPostprocessor = wallabyWebpack({
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel?presets[]=es2015'
      }
    ]
  },
  devtool: 'source-map'
});

Unless you must, consider not using webpack loaders in wallaby configuration, specifically those that require devtool and source maps, and use wallaby.js compilers or preprocessors instead as described below, because wallaby.js babel compilers/preprocessors are faster.

Loaders vs preprocessors/compilers

For your tests you don’t have to use the module bundler loaders; where possible, you may use wallaby.js preprocessors or, a compiler (which is recommended), instead. For example, TypeScript or CoffeeScript compiler or Babel compiler.

For example, if you are using ES6/JSX, instead of using babel-loader in the webpack configuration, you may specify wallaby.js compiler:

    files: [
      {pattern: 'src/**/*.js', load: false}
    ],

    tests: [
      {pattern: 'test/**/*Spec.js', load: false}
    ],

    compilers: {
      '**/*.js': wallaby.compilers.babel({ /* babel options if you don't have .babelrc */ })
    }

    postprocessor: wallabyPostprocessor

In this case, don’t forget to remove devtool and unused loaders if you are using external webpack config as wallaby webpack config, for example:

  var webpackConfig = require('./webpack.config');

  // removing babel-loader, we will use babel compiler instead, it's more performant
  webpackConfig.module.rules = webpackConfig.module.rules.filter(function(l){
    return l.use !== 'babel-loader';
  });

  delete webpackConfig.devtool;

  var wallabyPostprocessor = wallabyWebpack(webpackConfig);

TypeScript and CoffeeScript

Please note that if you are using CoffeeScript or TypeScript, then you need to use a built-in compiler instead of a loader, because with a loader in the postprocessor, it is too late for wallaby to instrument the resulting JavaScript. Compilers should just work without any additional configuration, unless you need to pass some compiler specific options for TypeScript or CoffeeScript.

You may find a sample of wallaby.js configuration for Webpack + TypeScript in this repository.

If you are importing an existing webpack config, then apart from removing the ts-loader for TypeScript, you’ll also need to remove .ts extension from the resolve.extensions because otherwise webpack will try use those during the compilation:

var webpackTestConfig = require('./config/webpack.test.js');
...
// for Webpack 1
webpackTestConfig.resolve.extensions = ['', '.js', '.json']; // anything you need but .ts extension

// for Webpack 2
webpackTestConfig.resolve.extensions = ['.js', '.json']; // anything you need but .ts extension

Files and tests

All source files and tests (except external files/libs) must have a load: false set, because wallaby will load wrapped versions of these files on window.__moduleBundler.loadTests() call in the setup function. Node modules should not be listed in the files list (unless you would like to load them globally as opposed to require/import them).

The order of source files does not matter, so patterns can be used instead of listing all the files.

The code inside each file is wrapped in such a way that, when the file is loaded in browser, it doesn’t execute the code immediately. Instead, it just adds some function that executes the file code to the test loader’s cache. Tests and dependent files are loaded from wallaby setup function, by calling __moduleBundler.loadTests(), and then executed.

Entry patterns

By default, wallaby.js uses your tests as entry points for Webpack compilation. The rest of files are just require-ed from test files.

If you would like to load other files into the test sandbox without having to require them, you may just specify those files in your files list with load: true (default) flag. These files will just be loaded into the sandbox via script tags.

Sometimes you may also want to load some files that require other files, so they need to be compiled with Webpack. To support the scenario, wallaby-webpack has a configuration option called entry patterns, where you may specify all entry points for Webpack compilation done by wallaby.js. In this case, it will be your tests and those other files that you would like to load (and compile with Webpack) before your tests.

For example, if you have a fixture.js file that requires other files of your app and you would like it to be compiled by Webpack and loaded into the sandbox, then your wallaby config may look like:

var wallabyWebpack = require('wallaby-webpack');
var wallabyPostprocessor = wallabyWebpack({
    entryPatterns: [
            'fixture.js',
            'src/**/*.spec.js'
            ]
});

return {
        files: [
            // test libraries (chai, sinon, sinon-chai)
            // NOTE that with npm >=3 the file structure may be different
            {pattern: 'node_modules/karma-sinon-chai/node_modules/chai/chai.js', instrument: false},
            {pattern: 'node_modules/karma-sinon-chai/node_modules/sinon/pkg/sinon.js', instrument: false},
            {pattern: 'node_modules/karma-sinon-chai/node_modules/sinon-chai/lib/sinon-chai.js', instrument: false},

            {pattern: 'src/**/*.js', load: false},
            {pattern: 'fixture.js', load: false},
            {pattern: 'src/**/*.spec.js', ignore: true}
        ],

        tests: [
            {pattern: 'src/**/*.spec.js', load: false}
        ],

        postprocessor: wallabyPostprocessor,

        // I'll assume that you are using ES6 and mocha
        '**/*.js': wallaby.compilers.babel({ /* babel options if you don't have .babelrc */ }),

        testFramework: 'mocha',

        setup: function () {
            window.expect = chai.expect;
            window.__moduleBundler.loadTests();
        }
    };

Please note, that if you are using default TypeScript compiler with entry patterns, you need to specify entry patterns with .js extension because the compiler is executed before the webpack postprocessor and it renames compiled files by default.

Module resolution issues

If you are observing ModuleNotFoundError, the required module folders are referenced in a relative manner and didn’t make it into the wallaby file cache that wallaby is using to run your tests.

For example, if you are using bower_components and may have something like this in your config:

    // for Webpack 1
    resolve: {
      modulesDirectories: ['bower_components']
    }

    // for Webpack 2
    resolve: {
      modules: ['bower_components']
    }

In this case, even though we would not recommend it, you may add { pattern: 'bower_components/**/*.*', instrument: false, load: false } to your files list, so that bower_components contents makes it into the wallaby cache and wallaby will be able to resolve modules from it.

The more efficient approach that we would recommend is to specify an absolute path in your wallaby configuration for webpack for your external modules (not your source files) instead:

    // for Webpack 1
    resolve: {
      modulesDirectories: [require('path').join(__dirname, 'bower_components')]
    }
    // for Webpack 2
    resolve: {
      modules: [require('path').join(__dirname, 'bower_components')]
    }

This way you don’t need to specify bower_components in your files list and wallaby will not have to copy it over to its cache.

The same applies to resolve.fallback, resolve.root and resolveLoader webpack configuration settings.

Please note that you don’t need to do something similar for node_modules, as wallaby-webpack automatically adds local project node_modules folder to the the fallback list. Unlike node_modules, bower_components and any other custom module folders can be used with different names/paths, so wallaby doesn’t try to automatically add them based on the name convention.

Also, you should not use the approach for your source files, because they need to be instrumented and served from the wallaby cache, not from the local project folder.

Absolute paths for wallaby cache folder

Because wallaby.js uses its own cache with your project instrumented files, sometimes you may need to specify wallaby cache folder. For example, if you are resolving folders as modules by setting resolve.root with Webpack 1, then using

'use strict';

let path = require('path'),
    wallabyWebpack = require('wallaby-webpack');

module.exports = (wallaby) => {
    let webpackPostprocessor = wallabyWebpack({
        resolve: {
            root: [
                path.join(wallaby.projectCacheDir, 'src')
            ]
        }
    });
    ...

or resolve.modules with Webpack 2:

'use strict';

let path = require('path'),
    wallabyWebpack = require('wallaby-webpack');

module.exports = (wallaby) => {
    let webpackPostprocessor = wallabyWebpack({
        resolve: {
            modules: [
                path.join(wallaby.projectCacheDir, 'src')
            ]
        }
    });
    ...

allows to have src/myModule/index.js file and reference it as a module from your tests:

import m from 'myModule';

The same approach may be required if you are using resolve.alias with Webpack 1 or with Webpack 2:

module.exports = (wallaby) => {
    let webpackPostprocessor = wallabyWebpack({
        resolve: {
            alias: {
                myModule: path.join(wallaby.projectCacheDir, 'myModule')
            }
        }
    });

Also, if you are using node: {__dirname: true, __filename: true} in your webpack config to be able to use __dirname or __filename variables in your source files, then you will need to set Webpack context to be wallaby project cache directory path.

module.exports = function () {
  return {

    files: [
      {pattern: 'src/**/*.js', load: false}
    ],

    tests: [
      {pattern: 'test/**/*Spec.js', load: false}
    ],

    postprocessor: wallabyWebpack({
                       ...
                       context: path.join(wallaby.projectCacheDir, 'src');
    }),

    setup: function () {
      window.__moduleBundler.loadTests();
    }
  };
};

More configuration examples

You may find a few projects with wallaby.js configuration for Webpack below.

You may also try searching GitHub for wallaby-webpack to find more projects with wallaby.js configuration for Webpack and use them as examples to create your own.