Writing Tests for Custom Matchers in Jest
Tags: jest, testing, javascript, tdd
Published: 2022-07-07 18:13:00

**TLDR:** Here's an example of how to write a test for a custom matcher:

```javascript
describe("toHaveDevDependency", () => {
  it("fails when given a number", () => {
    expect(() => {
      expect(2).toHaveDevDependency("node");
    }).toThrow("Expected 2 to be a YeomanTest.RunResult");
  );
});
```

## Motivation
In the spirit of [TDD](https://en.wikipedia.org/wiki/Test-driven_development), I found myself wanting to write tests to ensure my Jest custom matchers work properly.

A quick Google Search for "Jest write tests for custom matcher" only produced results about writing custom matchers themselves when really I wanted to see examples of how to write tests that exercise my custom matchers.

After figuring out how to do this on my own, I thought I'd write the missing article :).

## Background
If you're familiar with [Jest](https://jestjs.io/), you're likely also familiar with [custom matchers](https://everyday.codes/javascript/create-custom-jest-matchers-to-test-like-a-pro/).

If not, here's the summary version: custom matchers allow you to add your own matching assertions for `expect` statements. This can be _super_ useful when you find yourself writing the same testing code over and over again and want to DRY up your tests.

## Example Situation
This happened recently when writing a [Yeoman generator](https://yeoman.io/) to quickly scaffold new projects. I wanted to write something like `expect(result).toHaveDevDependency("typescript")` to assert that the `package.json` file generated with the project includes a specified package in its `devDependencies`.

The testing helpers provided by Yeoman in [`yeoman-test`](https://www.npmjs.com/package/yeoman-test) include a useful `assertJsonFileContent(fileName: string, content: any)` method, but as I found myself writing things like

```javascript
result.assertJsonFileContent(
  "package.json",
  { 
    devDependencies: {
      typescript: "4.7.4"
    }
  },
);
```

over and over I got tired of the verbosity and realized that this line doesn't state my intent as clearly as it could.

I wanted something like this instead:

```javascript
expect(result).toHaveDevDependency("typescript", "4.7.4");
```

Simple, clear, and clean.

My `toHaveDevDependency` custom matcher ended up being reasonably complicated, including checks to ensure that the received object in the `expect` call was, indeed, a `YeomanTest.RunResult` object, and then wrapping the `result.assertJsonFileContent()` call with a `try`/`catch` to translate that result into either a passing or failing `jest.CustomMatcherResult` with a useful message.

Ironically, the custom matcher ended up showing up in my code coverage reports as the least tested part of my codebase.

## The Solution
I wrote myself a test suite for `toHaveDevDependency` and in the process, I had to figure out how to test an `expect` statement itself. Rather meta.

### First Attempt: wrap `expect` call in a `try`/`catch`
My first attempt looked something like this:

```javascript
describe("toHaveDevDependency", () => {
  it("fails when given a number", () => {
    try {
      expect(2).toHaveDevDependency("node");
    } catch (error) {
      expect(error.message).toContain("JestAssertionError");
    }
  );
});
```

The idea being that Jest is likely throwing some kind of error when an `expect` assertion fails, and perhaps I can catch that error and then do something with it.

Unfortunately, this does not work. Jest seems to be smart enough to fail the test immediately, and while I can catch the error, I couldn't find an easy way to stop the test from failing down this path. There might be a way, but I gave up before I found it.

### Second Attempt: wrap raw `expect` call with `expect().toThrow()`
My next idea was to use the `expect().toThrow()` method to tell Jest explicitly that I'm expecting an error throw. It looked like this:

```javascript
describe("toHaveDevDependency", () => {
  it("fails when given a number", () => {
    expect(
      expect(2).toHaveDevDependency("node");
    ).toThrow("Expected 2 to be a YeomanTest.RunResult");
  );
});
```

This didn't work, either. The test failed immediately.

### Final Working Attempt: wrap `expect` call in an arrow function and wrap that in an `expect().toThrow()`
I had one more idea: what if I wrap the `expect` call in an arrow function first?

This looked like this:
```javascript
describe("toHaveDevDependency", () => {
  it("fails when given a number", () => {
    expect(() => {
      expect(2).toHaveDevDependency("node");
    }).toThrow("Expected 2 to be a YeomanTest.RunResult");
  );
});
```

This works! I haven't looked into the Jest internals enough to understand exactly why, but intuitively this makes sense: the arrow function insulates the inner call to `expect` from the Jest test context, and prevents the function call from being executed immediately within the `it` block.

Now I can happily write test suites for my custom matchers and test everything with confidence!