Jest Matchers

Jest Matchers are functions used in Jest tests to make assertions about expected outcomes. They compare received values against expected values, returning pass or fail results.

Detailed explanation

Jest matchers are the core of writing effective and readable tests in Jest, a popular JavaScript testing framework. They provide a declarative way to assert that your code behaves as expected. Instead of manually writing complex conditional statements to check for expected values, matchers offer a concise and expressive syntax. This makes tests easier to write, understand, and maintain.

Matchers are chained onto the expect() function, which takes the value you want to test as its argument. The matcher then performs a specific comparison or assertion against that value.

Here's a basic example:

test('adds 1 + 2 to equal 3', () => {
  expect(1 + 2).toBe(3);
});

In this example, expect(1 + 2) sets up the expectation, and .toBe(3) is the matcher. The toBe matcher performs a strict equality comparison (using ===) between the received value (the result of 1 + 2) and the expected value (3).

Commonly Used Matchers

Jest provides a rich set of built-in matchers to cover a wide range of testing scenarios. Here are some of the most frequently used ones:

  • .toBe(value): As shown above, performs a strict equality comparison.

  • .toEqual(value): Checks for deep equality. Useful for comparing objects and arrays, where you want to ensure that the contents are the same, not just the references.

    test('object assignment', () => {
      const data = {one: 1};
      data['two'] = 2;
      expect(data).toEqual({one: 1, two: 2});
    });
  • .toBeNull(): Checks if a value is null.

  • .toBeUndefined(): Checks if a value is undefined.

  • .toBeDefined(): Checks if a value is not undefined.

  • .toBeTruthy(): Checks if a value is truthy (evaluates to true in a boolean context).

  • .toBeFalsy(): Checks if a value is falsy (evaluates to false in a boolean context).

  • .toBeGreaterThan(number): Checks if a value is greater than a given number.

  • .toBeLessThan(number): Checks if a value is less than a given number.

  • .toBeGreaterThanOrEqual(number): Checks if a value is greater than or equal to a given number.

  • .toBeLessThanOrEqual(number): Checks if a value is less than or equal to a given number.

  • .toBeCloseTo(number, numDigits?): Compares floating-point numbers for approximate equality. Due to the nature of floating-point arithmetic, direct equality comparisons can be unreliable. numDigits specifies the number of decimal places to round to when comparing.

    test('adding floating point numbers', () => {
      const value = 0.1 + 0.2;
      //expect(value).toBe(0.3); // This will fail!
      expect(value).toBeCloseTo(0.3); // This works.
    });
  • .toMatch(regexp): Checks if a string matches a regular expression.

    test('there is a "stop" in Christoph', () => {
      expect('Christoph').toMatch(/stop/);
    });
  • .toContain(item): Checks if an array or string contains a specific item.

    const shoppingList = [
      'diapers',
      'kleenex',
      'trash bags',
      'paper towels',
      'milk',
    ];
     
    test('the shopping list has milk on it', () => {
      expect(shoppingList).toContain('milk');
      expect(new Set(shoppingList)).toContain('milk');
    });
  • .toThrow(error?): Checks if a function throws an error. You can optionally provide an error type or message to check for a specific error.

    const compileAndroidCode = () => {
      throw new Error('you are using the wrong JDK');
    };
     
    test('compiling android goes as expected', () => {
      expect(() => compileAndroidCode()).toThrow();
      expect(() => compileAndroidCode()).toThrow(Error);
     
      // You can also use the exact error message or a regexp
      expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
      expect(() => compileAndroidCode()).toThrow(/JDK/);
    });

Negating Matchers with .not

You can use the .not modifier to invert the meaning of any matcher. For example:

test('adds 1 + 2 to NOT equal 4', () => {
  expect(1 + 2).not.toBe(4);
});

This test will pass because 1 + 2 does not equal 4.

Custom Matchers

While Jest's built-in matchers are powerful, you may sometimes need to create your own custom matchers to handle specific testing scenarios in your application. This is particularly useful when you have complex business logic or data structures that require specialized assertions.

To create a custom matcher, you use the expect.extend() method. This method takes an object where each key is the name of your custom matcher and the value is a function that implements the matcher's logic.

expect.extend({
  toBeWithinRange(received, min, max) {
    const pass = received >= min && received <= max;
    if (pass) {
      return {
        message: () =>
          `expected ${received} not to be within range ${min} - ${max}`,
        pass: true,
      };
    } else {
      return {
        message: () =>
          `expected ${received} to be within range ${min} - ${max}`,
        pass: false,
      };
    }
  },
});
 
test('numeric ranges', () => {
  expect(100).toBeWithinRange(90, 110);
  expect(101).not.toBeWithinRange(0, 100);
  expect(() => expect(100).toBeWithinRange(110, 200)).toThrowError(
    'The second argument of .toBeWithinRange(min, max) must be smaller than the third argument. Received min: 110, max: 200',
  );
});

The custom matcher function receives the value being tested (received) and any arguments passed to the matcher (e.g., min and max in the toBeWithinRange example). It must return an object with two properties:

  • pass: A boolean indicating whether the assertion passed or failed.
  • message: A function that returns a string explaining the reason for the failure. This message should be clear and helpful for debugging. It should also handle both the positive and negative cases (i.e., what the value was expected to be when the assertion failed).

Best Practices

  • Write clear and concise tests: Use matchers that accurately reflect the behavior you are testing.
  • Provide helpful error messages: When a test fails, the error message should clearly indicate the expected and actual values.
  • Use custom matchers when appropriate: Don't hesitate to create custom matchers to simplify complex assertions and improve code readability.
  • Test edge cases: Ensure your tests cover a wide range of inputs and scenarios, including edge cases and boundary conditions.
  • Keep tests independent: Each test should be self-contained and not rely on the state of other tests.

By mastering Jest matchers, you can write robust and maintainable tests that ensure the quality and reliability of your JavaScript code.

Further reading