jestjs/jest · error · TypeError

expect.extend: `${key}` is not a valid matcher. Must be a fu

Error message

expect.extend: `${key}` is not a valid matcher. Must be a function, is "${getType(matcher)}"

What it means

setMatchers (called by expect.extend) iterates each key and verifies `typeof matcher === 'function'`; a non-function value throws TypeError with the actual type. Jest builds an asymmetric matcher variant and registers the matcher for dispatch, both of which require the value to be callable.

Source

Thrown at packages/expect/src/jestMatchersObject.ts:65

export const setState = <State extends MatcherState = MatcherState>(
  state: Partial<State>,
): void => {
  Object.assign((globalThis as any)[JEST_MATCHERS_OBJECT].state, state);
};

export const getMatchers = (): MatchersObject =>
  (globalThis as any)[JEST_MATCHERS_OBJECT].matchers;

export const setMatchers = (
  matchers: MatchersObject,
  isInternal: boolean,
  expect: Expect,
): void => {
  for (const key of Object.keys(matchers)) {
    const matcher = matchers[key];

    if (typeof matcher !== 'function') {
      throw new TypeError(
        `expect.extend: \`${key}\` is not a valid matcher. Must be a function, is "${getType(
          matcher,
        )}"`,
      );
    }

    Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {
      value: isInternal,
    });

    if (!isInternal) {
      // expect is defined

      class CustomMatcher extends AsymmetricMatcher<
        [unknown, ...Array<unknown>]
      > {
        constructor(inverse = false, ...sample: [unknown, ...Array<unknown>]) {
          super(sample, inverse);

View on GitHub (pinned to f49721c78e)

Solutions

  1. Make every value in the object a function with signature (this: MatcherContext, received, ...args) => result.
  2. Verify each import resolves to a function (check named/default export names).
  3. If you intended a namespace, pass each function individually: expect.extend({ foo: ns.foo }).

Example fix

// before — default import mistake or placeholder
import matchers from './matchers'; // undefined default export
expect.extend({ toBeFoo: matchers }); // matchers is undefined

// after
import { toBeFoo } from './matchers';
expect.extend({ toBeFoo });
Defensive patterns

Strategy: type-guard

Validate before calling

for (const [k, v] of Object.entries(matchers)) {
  if (typeof v !== 'function') {
    throw new TypeError(`expect.extend: ${k} is not a function`);
  }
}
expect.extend(matchers);

Type guard

function isMatchersObject(x: Record<string, unknown>): x is Record<string, (...args: unknown[]) => unknown> {
  return Object.values(x).every(v => typeof v === 'function');
}

Try / catch

// setMatchers throws synchronously during expect.extend — validate the input map first

Prevention

When it happens

Trigger: Calling expect.extend({ foo: 42 }), expect.extend({ foo: null }), expect.extend({ foo: 'some string' }), expect.extend({ foo: { bar() {} } }) (object instead of the function), or importing a matcher from a module that did not export it (undefined).

Common situations: Wrong/named import (importing a non-existent export gives undefined); passing a config object instead of the matcher function; copy-paste leaving a placeholder; spreading a partial object whose values aren't functions.

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/fff84e3eaa23c900.json. Report an issue: GitHub.