Skip to content

[Bug]: expect.any fails for Array and for other object instances when they come from different realms (Asymmetric matcher cross-realm issue) #15597

@sgleisner

Description

@sgleisner

Version

29.7.0

Steps to reproduce

  1. Clone https://github.com/sgleisner/jest-cross-realm-expect-any-example
  2. yarn install
  3. yarn test

Alternatively, fork this sandbox and it should run tests on startup: https://codesandbox.io/p/github/sgleisner/jest-cross-realm-expect-any-example/main

Expected behavior

expect.any(Array) should succeed for the ["apple", "banana", "cherry"] value in both tests.

Actual behavior

expect.any(Array) succeds for ["apple", "banana", "cherry"] when the array comes from the same realm, but when it comes from another realm, fails with the following error:

expect(received).toMatchObject(expected)

- Expected  - 1
+ Received  + 5

  Object {
-   "details": Any<Array>,
+   "details": Array [
+     "apple",
+     "banana",
+     "cherry",
+   ],  

Additional context

To reproduce the issue, I created a function that returns a fetch Response, which operates in a different realm than the test function.

The problem lies in the asymmetricMatch method:

// v29.7.0
asymmetricMatch(other: unknown) {
  if (this.sample == String) {
    return typeof other == 'string' || other instanceof String;
  }

  if (this.sample == Number) {
    return typeof other == 'number' || other instanceof Number;
  }

  if (this.sample == Function) {
    return typeof other == 'function' || other instanceof Function;
  }

  if (this.sample == Boolean) {
    return typeof other == 'boolean' || other instanceof Boolean;
  }

  if (this.sample == BigInt) {
    return typeof other == 'bigint' || other instanceof BigInt;
  }

  if (this.sample == Symbol) {
    return typeof other == 'symbol' || other instanceof Symbol;
  }

  if (this.sample == Object) {
    return typeof other == 'object';
  }

  return other instanceof this.sample;
}

For Array, it defaults to other instanceof this.sample, which is false when sample is Array and other is an instance of Array from another realm.

Note that this was fixed in #15101 and released in v30.0.0-alpha.6 by correctly using Array.isArray:

// v30.0.0-alpha.6
asymmetricMatch(other: unknown) {
  if (this.sample === String) {
    return typeof other === 'string' || other instanceof String;
  }

  if (this.sample === Number) {
    return typeof other === 'number' || other instanceof Number;
  }

  if (this.sample === Function) {
    return typeof other === 'function' || other instanceof Function;
  }

  if (this.sample === Boolean) {
    return typeof other === 'boolean' || other instanceof Boolean;
  }

  if (this.sample === BigInt) {
    return typeof other === 'bigint' || other instanceof BigInt;
  }

  if (this.sample === Symbol) {
    return typeof other === 'symbol' || other instanceof Symbol;
  }

  if (this.sample === Object) {
    return typeof other === 'object';
  }

  if (this.sample === Array) {
    return Array.isArray(other);
  }

  return other instanceof this.sample;
}

Should this fix be backported to v29?

Environment

System:
    OS: Linux 6.1 Debian GNU/Linux 12 (bookworm) 12 (bookworm)
    CPU: (2) x64 AMD EPYC
  Binaries:
    Node: 20.12.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.5.0 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/share/npm-global/bin/pnpm
  npmPackages:
    jest: ^29.7.0 => 29.7.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions