{"record":{"id":"b7f699f68ed08614","repo":"vitest-dev/vitest","slug":"expected-must-be-a-function-received-typeof-exp","errorCode":null,"errorMessage":"expected must be a function, received ${typeof expected}","messagePattern":"expected must be a function, received (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/integrations/snapshot/chai.ts","lineNumber":30,"sourceCode":"import { getNames } from '../../utils/tasks'\n\nlet _client: SnapshotClient\n\nexport function getSnapshotClient(): SnapshotClient {\n  if (!_client) {\n    _client = new SnapshotClient({\n      isEqual: (received, expected) => {\n        return equals(received, expected, [iterableEquality, subsetEquality])\n      },\n    })\n  }\n  return _client\n}\n\nfunction getError(expected: () => void | Error, promise: string | undefined) {\n  if (typeof expected !== 'function') {\n    if (!promise) {\n      throw new Error(\n        `expected must be a function, received ${typeof expected}`,\n      )\n    }\n\n    // when \"promised\", it receives thrown error\n    return expected\n  }\n\n  try {\n    expected()\n  }\n  catch (e) {\n    return e\n  }\n\n  throw new Error('snapshot function didn\\'t throw')\n}\n","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/integrations/snapshot/chai.ts#L12-L48","documentation":"Thrown inside the snapshot helper `getError` when the value handed to `toThrowErrorMatchingSnapshot` / `toThrowErrorMatchingInlineSnapshot` is not a function AND the assertion is not in promise mode (i.e. no `rejects`/`throws` promise flag). In the non-promise path Vitest expects a callable that throws, so a non-function value is a usage error.","triggerScenarios":"Writing `expect(myError).toThrowErrorMatchingSnapshot()` (passing an Error instance directly) instead of `expect(() => { throw myError }).toThrowErrorMatchingSnapshot()`; passing a string or object literal to the assertion; using the assertion on a non-throwing expression.","commonSituations":"Confusing `expect(...).toThrow()` style (which wraps a function) with passing the error itself; refactor that moved the throw out of an arrow function; copy-pasting from `toThrow` examples without the function wrapper.","solutions":["Wrap the throwing code in a function: `expect(() => { throw new Error('x') }).toThrowErrorMatchingSnapshot()`.","If asserting on a rejected promise, use the promise form: `await expect(promise).rejects.toThrowErrorMatchingSnapshot()`.","Double-check you are not passing an already-caught error object."],"exampleFix":"// before\nconst err = new Error('boom')\nexpect(err).toThrowErrorMatchingSnapshot()\n\n// after\nexpect(() => { throw new Error('boom') }).toThrowErrorMatchingSnapshot()","handlingStrategy":"type-guard","validationCode":"function snapshotThrow(fn) {\n  if (typeof fn !== 'function') throw new Error('pass a function that throws')\n  return expect(fn).toThrowErrorMatchingSnapshot()\n}","typeGuard":"function isThrowingFn(v): v is () => void {\n  return typeof v === 'function'\n}","tryCatchPattern":null,"preventionTips":["Always wrap throwing code in an arrow function for toThrowErrorMatching* assertions.","For rejected promises, use expect(p).rejects.toThrowErrorMatchingSnapshot().","Never pass an already-caught Error object to these assertions."],"tags":["snapshot","tothrowerrormatchingsnapshot","chai-assertion","api-misuse"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}