{"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/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/integrations/snapshot/chai.ts#L12-L48","documentation":"`toThrowErrorMatchingSnapshot` / `toThrowErrorMatchingInlineSnapshot` call `getError(expected, promise)` which expects the assertion's subject to be a function it can invoke to capture the thrown error. When not in a `resolves`/`rejects` promise context and the subject is not a function, there is nothing to execute, so it throws `expected must be a function, received <type>`.","triggerScenarios":"Writing `expect(<non-function>).toThrowErrorMatchingSnapshot()` / `...InlineSnapshot()` outside of an async `resolves`/`rejects` chain — e.g. `expect(42).toThrowErrorMatchingSnapshot()` or `expect('err').toThrowErrorMatchingInlineSnapshot()`.","commonSituations":"Forgetting to wrap the throwing call in a function, or confusing `toThrowError*` matchers (which need a callable) with value matchers.","solutions":["Wrap the code that should throw in a function: `expect(() => risky()).toThrowErrorMatchingSnapshot()`.","For async rejection snapshots, use `await expect(promise).rejects.toThrowErrorMatchingSnapshot()` so the promise path is used.","Ensure the matcher name actually matches your intent — use `toMatchSnapshot()` for non-throw values."],"exampleFix":"// before\nexpect(loadUser(-1)).toThrowErrorMatchingSnapshot()\n// after\nexpect(() => loadUser(-1)).toThrowErrorMatchingSnapshot()","handlingStrategy":"type-guard","validationCode":"function assertThrowsSnapshot(fn: () => unknown) {\n  if (typeof fn !== 'function') throw new TypeError('expected must be a function')\n  expect(fn).toThrowErrorMatchingSnapshot()\n}","typeGuard":"function isCallable(v: unknown): v is (...a: any[]) => any { return typeof v === 'function' }","tryCatchPattern":null,"preventionTips":["Always wrap the throwing code in `() => { ... }` for `toThrowError*Snapshot`.","For async rejection snapshots use `await expect(p).rejects.toThrowErrorMatchingSnapshot()`."],"tags":["snapshot","matcher","throw","assertion"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}