Can't reproduce doc example for Jest custom matcher: property doesn't exist on type "Expect"

Viewed 293

I try to create a custom helper matcher that can be used like arrayContaining directly from the expect object without passing it a value to compare to.

On the official documentation for expect.extend(), there is an example that match my needs:

export {}

declare global {
  namespace jest {
    interface Matchers<R> {
      toBeWithinRange(a: number, b: number): R;
    }
  }
}

expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () =>
          `expected ${received} not to be within range ${floor} - ${ceiling}`,
        pass: true,
      };
    } else {
      return {
        message: () =>
          `expected ${received} to be within range ${floor} - ${ceiling}`,
        pass: false,
      };
    }
  },
});

test('numeric ranges', () => {
  expect(100).toBeWithinRange(90, 110);
  expect(101).not.toBeWithinRange(0, 100);
  expect({apples: 6, bananas: 3}).toEqual({
    apples: expect.toBeWithinRange(1, 10), // <-- What I need
    bananas: expect.not.toBeWithinRange(11, 20),
  });
});

However I can't reproduce the example even with a minimal setup. I still got the following error:

apples: expect.toBeWithinRange(1, 10), // property 'toBeWithinRange' doesn't exist on type 'Expect'. (ts2339)

I did install @types/jest and added the global declaration without success.

What bit of configuration am I missing?

1 Answers

I was missing a type definition for the direct call to expect.toBeWithinRange(). Here is the final working example for me:

// customMatcher.ts

declare global {
  namespace jest {
    interface Matchers<R> {
      toBeWithinRange(a: number, b: number): R;
    }
    interface Expect {
      toBeWithinRange(a: number, b: number): any;
    }

    interface InverseAsymmetricMatchers {
      toBeWithinRange(a: number, b: number): any;
    }
  }
}

export function toBeWithinRange(received, floor, ceiling) {
  const pass = received >= floor && received <= ceiling;
  if (pass) {
    return {
      message: () =>
        `expected ${received} not to be within range ${floor} - ${ceiling}`,
      pass: true,
    };
  } else {
    return {
      message: () =>
        `expected ${received} to be within range ${floor} - ${ceiling}`,
      pass: false,
    };
  }
}

expect.extend({
  toBeWithinRange,
});

And in order to use the custom matcher in another file you need to import its definition:

// customMatcher.spec.ts

import './customMatcher.ts';

test('numeric ranges', () => {
  expect(100).toBeWithinRange(90, 110);
  expect(101).not.toBeWithinRange(0, 100);
  expect({apples: 6, bananas: 3}).toEqual({
    apples: expect.toBeWithinRange(1, 10), // <-- What I need
    bananas: expect.not.toBeWithinRange(11, 20),
  });
});
Related