{"id":"fff84e3eaa23c900","repo":"jestjs/jest","slug":"expect-extend-key-is-not-a-valid-matcher-mu","errorCode":null,"errorMessage":"expect.extend: `${key}` is not a valid matcher. Must be a function, is \"${getType(matcher)}\"","messagePattern":"expect\\.extend: `(.+?)` is not a valid matcher\\. Must be a function, is \"(.+?)\"","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/expect/src/jestMatchersObject.ts","lineNumber":65,"sourceCode":"export const setState = <State extends MatcherState = MatcherState>(\n  state: Partial<State>,\n): void => {\n  Object.assign((globalThis as any)[JEST_MATCHERS_OBJECT].state, state);\n};\n\nexport const getMatchers = (): MatchersObject =>\n  (globalThis as any)[JEST_MATCHERS_OBJECT].matchers;\n\nexport const setMatchers = (\n  matchers: MatchersObject,\n  isInternal: boolean,\n  expect: Expect,\n): void => {\n  for (const key of Object.keys(matchers)) {\n    const matcher = matchers[key];\n\n    if (typeof matcher !== 'function') {\n      throw new TypeError(\n        `expect.extend: \\`${key}\\` is not a valid matcher. Must be a function, is \"${getType(\n          matcher,\n        )}\"`,\n      );\n    }\n\n    Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {\n      value: isInternal,\n    });\n\n    if (!isInternal) {\n      // expect is defined\n\n      class CustomMatcher extends AsymmetricMatcher<\n        [unknown, ...Array<unknown>]\n      > {\n        constructor(inverse = false, ...sample: [unknown, ...Array<unknown>]) {\n          super(sample, inverse);","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/expect/src/jestMatchersObject.ts#L47-L83","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Make every value in the object a function with signature (this: MatcherContext, received, ...args) => result.","Verify each import resolves to a function (check named/default export names).","If you intended a namespace, pass each function individually: expect.extend({ foo: ns.foo })."],"exampleFix":"// before — default import mistake or placeholder\nimport matchers from './matchers'; // undefined default export\nexpect.extend({ toBeFoo: matchers }); // matchers is undefined\n\n// after\nimport { toBeFoo } from './matchers';\nexpect.extend({ toBeFoo });","handlingStrategy":"type-guard","validationCode":"for (const [k, v] of Object.entries(matchers)) {\n  if (typeof v !== 'function') {\n    throw new TypeError(`expect.extend: ${k} is not a function`);\n  }\n}\nexpect.extend(matchers);","typeGuard":"function isMatchersObject(x: Record<string, unknown>): x is Record<string, (...args: unknown[]) => unknown> {\n  return Object.values(x).every(v => typeof v === 'function');\n}","tryCatchPattern":"// setMatchers throws synchronously during expect.extend — validate the input map first","preventionTips":["Type expect.extend's argument as Record<string, MatcherFunction>.","Verify named imports exist before bundling them into extend.","Use a typed helper that rejects non-function values at the boundary."],"tags":["expect","expect-extend","custom-matcher","type-error"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}