{"id":"ac82d9967a25e533","repo":"vitest-dev/vitest","slug":"expected-is-not-a-string","errorCode":null,"errorMessage":"Expected is not a string","messagePattern":"Expected is not a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/expect/src/jest-asymmetric-matchers.ts","lineNumber":77,"sourceCode":"}\n\n// implement custom chai/loupe inspect for better AssertionError.message formatting\n// https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29\n// @ts-expect-error computed properties is not supported when isolatedDeclarations is enabled\n// FIXME: https://github.com/microsoft/TypeScript/issues/61068\nAsymmetricMatcher.prototype[Symbol.for('chai/inspect')] = function (options: { depth: number; truncate: number }): string {\n  // minimal pretty-format with simple manual truncation\n  const result = stringify(this, options.depth, { min: true })\n  if (result.length <= options.truncate) {\n    return result\n  }\n  return `${this.toString()}{…}`\n}\n\nexport class StringContaining extends AsymmetricMatcher<string> {\n  constructor(sample: string, inverse = false) {\n    if (!isA('String', sample)) {\n      throw new Error('Expected is not a string')\n    }\n\n    super(sample, inverse)\n  }\n\n  asymmetricMatch(other: string): boolean {\n    const result = isA('String', other) && other.includes(this.sample)\n\n    return this.inverse ? !result : result\n  }\n\n  toString() {\n    return `String${this.inverse ? 'Not' : ''}Containing`\n  }\n\n  getExpectedType() {\n    return 'string'\n  }","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/expect/src/jest-asymmetric-matchers.ts#L59-L95","documentation":"Thrown by the StringContaining asymmetric matcher constructor when the sample is not a string (checked via Object.prototype.toString). StringContaining tests substring inclusion, so a non-string expected value is invalid at construction time.","triggerScenarios":"Constructing expect.stringContaining(value) where value is a number, object, null, etc. The isA('String', sample) guard fails and the constructor throws before any match.","commonSituations":"Passing a non-string by mistake (e.g. expect.stringContaining(123) or a variable typed as any that holds a number); refactoring where the expected value type changed.","solutions":["Ensure the argument to expect.stringContaining is a string.","Coerce or stringify the value if substring semantics are intended.","For numeric/object checks, use the appropriate matcher (objectContaining, arrayContaining)."],"exampleFix":"// before\nexpect(obj).toEqual({ code: expect.stringContaining(404) })\n// after\nexpect(obj).toEqual({ code: expect.stringContaining('NF') })","handlingStrategy":"type-guard","validationCode":"function asString(v: unknown): string {\n  if (typeof v !== 'string') throw new TypeError(`stringContaining needs a string, got ${typeof v}`)\n  return v\n}\nexpect(obj).toEqual({ msg: expect.stringContaining(asString(sub)) })","typeGuard":"function isString(v: unknown): v is string {\n  return typeof v === 'string'\n}","tryCatchPattern":null,"preventionTips":["Type variables passed to stringContaining as string.","Add a unit test asserting the sample is a string in shared assertion helpers.","Avoid using any-typed values as matcher samples."],"tags":["expect","asymmetric-matcher","assertion","typescript"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}