How to get fast unit tests with(out) Webpack

It’s easy: don’t use Webpack. For unit testing that is.

There’s a ton of articles all over the web how to set up Webpack for your development environment (which hopefully includes testing as well), and all come down to using the same set of tools, or more importantly, one in particular: Karma.

If you don’t know what Karma is, it’s a widely popular test runner, introduced by the team behind AngularJS. It can do all kind of wild stuff, such as pre-processing (which we need for Webpack), source maps, coverage reports, headless (or real) browsers and all kinds of other bells and whistles which all come at a great cost: it’s slow as hell.

But, when you think about it, unit tests don’t really require all the said bells and whistles, they just need to run fast on every save so we see what’s going on with our code. We can check coverage and whatnot later.

Mocha to the rescue!

While mocha is more of a testing framework (and not a runner, like Karma), it also comes with an executable file that lets you run tests in the command line. But! The code we wrote for Webpack contains a lot of really Webpack-specific things like requiring stylesheets (yeah, I know, wtf), aliases, custom environment variables, loaders, etc.

This means mocha from the command line is out of the picture. Luckily, we can invoke it programatically!

But I need a browser!?

No you don’t. Unit testing has nothing to do with browsers, what you need is the DOM (for, say, testing React components) and since we’re using JavaScript all across the board, jsdom comes as a perfect solution. It sets up the environment to be browser-like, so that React (or any other library/framework of your preference) can do it’s magic, thinking it’s rendering to a real browser.

  • Testing framework? Check.
  • Fake browser? Check.
  • Script to bring all of it together? Here it is.

What this script does

The first 14 lines just import the stuff we need for our script and I also decided to globalize some of the testing libraries (chai and sinon) so we don’t have to require them in each test. If you use React, you might want to do the same for it.

Line 23 helps us work around Webpack’s enhanced require which exposes a function on require (require.ensure) and since it’s a test environment which most likely doesn’t have the polyfill we’re usually requiring, we just go ahead and require it.

Lines 30–43 is where the real magic happens, we take the original “require” method (provided by the Module module) and monkey patch it so that we can ignore non-JavaScript files and do other kinds of manipulation with requiring files. For example, you can have configuration files and instead of requiring the full path, you just require it’s name globally (in Webpack, it’s done via aliases).

On punching the duck
It’s important to note we are overwriting one of the core Node methods. This is almost always considered a bad practice (also known as monkey patching), but since this is a test environment which won’t come anywhere near production (and it increases our productivity) it won’t do any harm.

Lines 45–70 set up our fake browser; It creates a fake DOM and attaches all the variables and functions we’ll need globally. This ensures Mocha doesn’t complain about things.

Lines 77–83 is where our test suite gets ran. The key to this whole process working properly is on line 79: It clears the entire require cache before running the tests. In case you don’t know, every time we require a module in Node that module is automatically saved to memory and retrieved from there when needed. If we didn’t use this line, Mocha would read the file contents from memory and tests wouldn’t reflect our updated/saved code and the whole script would be unusable. Once we remove them from cache, Node re-requires them from disk and all is well.

Once we’re done removing the cache, we instantiate a new Mocha runner, add the files from our list and run tests on them.

But where do those files come from?

This is where chokidar comes in. It’s a file watcher that runs whatever you give it to run when one of the various file-related events occur. In lines 91–97 we observe two directories, the test directory and the source one: The latter just runs all the tests when a file is modified whereas the former builds the list of all files to test and also runs them when changes happen.

Save this file in the root of your project and run it like this:

$ babel-node mocha-runner.js

Use it, rejoice, and follow me on Twitter.

Show your support

Clapping shows how much you appreciated Tomaž Zaman’s story.