How to migrate from Mocha to Jest

How to migrate from Mocha to Jest

Jest is a simple testing library that works with zero configuration. Because of its great API - if you're already using Mocha - with a few find & replace and minimal effort, you can move to Jest without rewriting any of your tests.

In this post, I'll show you how I tweaked my existing Mocha setup to achieve this.

If you're new to Jest, don't forget to check out their Getting Started page as well.

I personally got hooked on this library when I realized it comes with a configuration option testEnvironment, that gives you a test environment with a JSDOM that works out of the box, without actually configuring JSDOM. I've lost count, across all projects, how many times I wanted to update the testing environment and ended up not doing it because I couldn't get JSDOM to work with my existing tests.

Let's jump straight into it!

Configuration

I use mocha.opts to configure Mocha. The contents of this file are important because I'm going to reference them later:

--reporter spec
--ui bdd
--colors
--file test_helpers/babel_loader.js
--file test_helpers/jsdom_setup.js
--file test_helpers/test_setup.js

and this is how I used to run Mocha:

mocha --opts mocha.opts

You can configure Jest with jest.config.js, a js or JSON file with the --config flag, and in package.json, see Configuring Jest. I suggest you start with package.json because I think that's the simplest. Once the configuration gets more complex, you can always move it into a separate file. In package.json you should use the key "jest" on the top level, like this:

{
  "name": "my-project",
  "jest": {
    "verbose": true
  }
}

now you should be able to run Jest as:

jest

Setup

This is the only place where depending on what other libraries you use in your tests, you might want to tweak their setup a bit.

In my case, test_helpers/test_setup.js had the environment setup for Mocha. If you're using Enzyme or Sinon for example, this is the place where you keep their configuration. Let's say your file has something similar:

global.sinonSandbox = sinon.createSandbox();
afterEach(function() {
  return global.sinonSandbox.restore();
});

Then you should be able to reuse this with Jest.

Jest does its environment setup with setupFilesAfterEnv. This option tells the library which file(s) to run before each test. Put this into package.json after verbose:

{
  "name": "my-project",
  "jest": {
    "verbose": true,
    "setupFilesAfterEnv": ["<rootDir>/test_helpers/test_setup.js"]
  }
}

Side-note: this doesn't exactly match the behavior of the Mocha configuration. Instead of creating only one sandbox, now we create one sandbox before each test. However, there should be no difference in how Sinon interacts with your tests.

Hooks

Mocha operates with a wide variety of hooks. I mostly used describe(), context(), it(), before(), after(), beforeEach(), and afterEach().

Jest uses a bit different naming, in general, you're safe to replace:

before ➡️ beforeAll

after ➡️ afterAll

context doesn't have an equivalent in Jest, so I decided to use describe. You can find & replace all occurrences of context, before, and after or put the following code into test_helpers/test_setup.js:

// mocha keyword mappings
window.context = describe;
window.before = beforeAll;
window.after = afterAll;

Function parameters

With Mocha, you were able to give hooks a description:

beforeEach(description, fn)
afterEach(description, fn)

in Jest, hooks only accept a function and an optional timeout:

beforeEach(fn, timeout)
afterEach(fn, timeout)

If your beforeEach blocks looked something like this:

beforeEach('login', () => {

to strip out the descriptions from every beforeEach run the following regular expression in your editor:

beforeEach\([^(]*

and replace it with

beforeEach(

Babel

We set up Babel in test_helpers/babel_loader.js for Mocha:

// Run before any test files are loaded to use Babel for subsequent parsing
require('@babel/register')({
  ignore: [/node_modules\//],
  extensions: ['.js', '.jsx', '.ts', '.tsx'],
});

You have to install babel-jest to use Babel with Jest:

yarn add --dev babel-jest

then add transfer to package.json that now should look like this:

{
  "name": "my-project",
  "jest": {
    "verbose": true,
    "setupFilesAfterEnv": ["<rootDir>/test_helpers/test_setup.js"],
    "transform": {
      "^.+\\.[t|j]sx?$": "babel-jest",
    }
  }
}

See more on using Babel with Jest.

At this point, you're pretty much ready to run your existing tests with Jest.

Conclusion

What I really liked about Jest is the "work great by default" philosophy.

Even when installed in an empty package and run, it gives you a meaningful output:

% jest
No tests found, exiting with code 1
Run with `--passWithNoTests` to exit with code 0
In /Users/akoskm/Projects/test
  2 files checked.
  testMatch: **/__tests__/**/*.[jt]s?(x), **/?(*.)+(spec|test).[tj]s?(x) - 0 matches
  testPathIgnorePatterns: /node_modules/ - 2 matches
  testRegex:  - 0 matches
Pattern:  - 0 matches

It doesn't ask for configuration files or necessary parameters, it simply works. I think this is a philosophy worth following when designing new tools.

What tools do you use to test your code?

Are you stuck in the middle of a migration? Let me know in the comments or find me on Twitter.

Did you find this article valuable?

Support Ákos Kőműves by becoming a sponsor. Any amount is appreciated!