vitest-dev/vitest · error · Error

Expected is not a string

Error message

Expected is not a string

What it means

The StringContaining asymmetric matcher (expect.stringContaining) requires its sample to be a string; isA('String', sample) returns false for any non-string and the constructor throws. This is a fail-fast construction-time check, not a match-time one.

Solutions

  1. Pass a plain string substring to expect.stringContaining.
  2. If you need pattern matching, use expect.stringMatching(regexOrString).
  3. Coerce or validate the value is a string before passing: String(sample) or a type guard.

Example fix

// before
expect(result).toEqual(expect.stringContaining(404))
// after
expect(result).toEqual(expect.stringContaining('Not Found'))
Defensive patterns

Strategy: type-guard

Validate before calling

function asStringContaining(sample) {
  if (typeof sample !== 'string') {
    throw new TypeError('expect.stringContaining needs a string')
  }
  return expect.stringContaining(sample)
}

Type guard

function isString(v): v is string {
  return typeof v === 'string'
}

Prevention

When it happens

Trigger: expect.stringContaining(sample) called with sample that is a number, object, null, etc.

Common situations: Passing a variable whose type is not narrowed (e.g. a config value typed as string | number); passing a RegExp where a substring was expected (use stringMatching instead).

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/ac82d9967a25e533. Report an issue: GitHub.

Appendix: source

Thrown at packages/expect/src/jest-asymmetric-matchers.ts:77

}

// implement custom chai/loupe inspect for better AssertionError.message formatting
// https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29
// @ts-expect-error computed properties is not supported when isolatedDeclarations is enabled
// FIXME: https://github.com/microsoft/TypeScript/issues/61068
AsymmetricMatcher.prototype[Symbol.for('chai/inspect')] = function (options: { depth: number; truncate: number }): string {
  // minimal pretty-format with simple manual truncation
  const result = stringify(this, options.depth, { min: true })
  if (result.length <= options.truncate) {
    return result
  }
  return `${this.toString()}{…}`
}

export class StringContaining extends AsymmetricMatcher<string> {
  constructor(sample: string, inverse = false) {
    if (!isA('String', sample)) {
      throw new Error('Expected is not a string')
    }

    super(sample, inverse)
  }

  asymmetricMatch(other: string): boolean {
    const result = isA('String', other) && other.includes(this.sample)

    return this.inverse ? !result : result
  }

  toString() {
    return `String${this.inverse ? 'Not' : ''}Containing`
  }

  getExpectedType() {
    return 'string'
  }

View on GitHub (pinned to 1fa9837ec2)