How to Add Custom Functions to a Jest Test Suite

Jest is a fantastic testing framework. Of the many I’ve used across different platforms and languages, it’s the best by a landslide. Jest is fast to learn, easy to use, full of features out of the box, and simple to customize.

One of the opportunities to customize your Jest test suite is to add custom functions to the test helpers. The Jest docs list several configuration options, and we can use those to our advantage while customizing our Jest test suite.

The Goal

I want easy-to-use, discoverable functions to add to the test suite. I don’t want to change much syntax from vanilla Jest, and I don’t want to import anything. Consider the following test:

 
describe("my test suite", () => {
  it("does something", () => {
    expect(1).toEqual(1);
  });
});

Say that the test is flakey, and I want to run it multiple times. Or there could be some code in the beforeEach or afterEach that could change the state of the test. Either way, it would be convenient to write the following to quickly debug it:

import { itRepeats } from "test/helpers";

describe("my test suite", () => {
  itRepeats(10, "does something", () => {
    expect(1).toEqual(1);
  });
  // -- or --
  itRepeats(10)("does something", () => {
    expect(1).toEqual(1);
  });
});

This pattern of testing has some drawbacks that I’d like to avoid. Preferably, I would write something like this:

describe("my test suite", () => {
  it.repeats(10, "does something", () => {
    expect(1).toEqual(1);
  });
  // -- or --
  it.repeats(10)("does something", () => {
    expect(1).toEqual(1);
  });
});

This would fit two of my goals: I don’t want the syntax of this helper to deviate far from Jest, and I don’t want to import any additional helper functions. Jest has some configuration options that make it pretty simple to implement this.

Configuring Jest

The Jest documentation specifies that the configuration could be added to jest.config.js, jest .config.json, or in the package.json, but for this example, we’ll use the jest.config.js.

Reading through the configuration options, we see the property setupFilesAfterEnv, which states:

A list of paths to modules that run some code to configure or set up the testing framework before each test. Since setupFiles executes before the test framework is installed in the environment, this script file presents you the opportunity of running some code immediately after the test framework has been installed in the environment.

Because it runs some code after Jest has been installed in the environment, we can use this to our advantage to customize (i.e. monkey-patch / duck-punch) Jest.

Add the following to your jest.config.js:

module.exports = {
  // Other configuration above...

  // Add the next three options if using TypeScript
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
  preset: "ts-jest",
  transform: {
    "^.+\\.tsx?$": "babel-jest",
  },

  // Run these files after jest has been
  // installed in the environment
  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], // use .js if you prefer JavaScript
};

Note: If you’ve configured Enzyme for Jest, then you may have already have done something like this before. Feel free to create a new file or reuse the same one.

At the root of the project, create a new file called jest.setup.ts. This file will be run after Jest has been installed into the environment. We can add custom functionality to Jest here:

test.repeats = async (
  times: number,
  name: string,
  fn?: jest.ProvidesCallback,
  timeout?: number,
) => {
  await Promise.all(
    Array(times)
      .fill(undefined)
      .map((_, i) => {
        return test(name, fn, timeout);
      }),
  );
};

If you’re using TypeScript, you might get a type error similar to Property 'repeats' does not exist on type 'It'.. We can fix that!

Create another file at the root of your project called jest.d.ts, and add the following to it:

/// <reference types="jest" />

declare namespace jest {
  interface It {
    repeats: (
      times: number,
      name: string,
      fn?: ProvidesCallback,
      timeout?: number,
    ) => void;
  }
}

Then, add the jest.d.ts line to the include property in your tsconfig.json:

{
  ...
  "include": [
    "jest.d.ts",
  ]
}

Now, the type errors should have gone away in the jest.setup.ts, and you should get type completion when you’re writing a `test` or `it` statement in your Jest test.

In this example, we added functionality to repeat a test multiple times. However, this is a bit of a contrived example, to show how everything gets wired up. In reality, we can customize Jest far beyond this.

The above code can be found here.