diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e23fc489f62..7aef1e0fd349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,10 @@ - `[jest-util]` Always load `mjs` files with `import` ([#15447](https://github.com/jestjs/jest/pull/15447)) - `[jest-worker]` Properly handle a circular reference error when worker tries to send an assertion fails where either the expected or actual value is circular ([#15191](https://github.com/jestjs/jest/pull/15191)) - `[jest-worker]` Properly handle a BigInt when worker tries to send an assertion fails where either the expected or actual value is BigInt ([#15191](https://github.com/jestjs/jest/pull/15191)) +- `[expect]` Resolve issue where `ObjectContaining` matched non-object values. ([#15463])(https://github.com/jestjs/jest/pull/15463). + - Adds a `conditional/check` to ensure the argument passed to `expect` is an object. + - Add unit tests for new `ObjectContaining` behavior. + - Remove `invalid/wrong` test case assertions for `ObjectContaining`. ### Performance diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index 2a1748e27741..2a39c405693b 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -184,7 +184,6 @@ test('ArrayNotContaining throws for non-arrays', () => { test('ObjectContaining matches', () => { const foo = Symbol('foo'); for (const test of [ - objectContaining({}).asymmetricMatch('jest'), objectContaining({foo: 'foo'}).asymmetricMatch({foo: 'foo', jest: 'jest'}), objectContaining({foo: undefined}).asymmetricMatch({foo: undefined}), objectContaining({first: objectContaining({second: {}})}).asymmetricMatch({ @@ -247,6 +246,18 @@ test('ObjectContaining throws for non-objects', () => { ); }); +test('ObjectContaining does not match when non-objects are passed to the expect function as arguments', () => { + for (const test of [ + objectContaining({}).asymmetricMatch('jest'), + objectContaining({}).asymmetricMatch(10), + objectContaining({}).asymmetricMatch(false), + objectContaining({}).asymmetricMatch(undefined), + objectContaining({}).asymmetricMatch([]), + ]) { + jestExpect(test).toEqual(false); + } +}); + test('ObjectContaining does not mutate the sample', () => { const sample = {foo: {bar: {}}}; const sample_json = JSON.stringify(sample); @@ -259,8 +270,6 @@ test('ObjectNotContaining matches', () => { const foo = Symbol('foo'); const bar = Symbol('bar'); for (const test of [ - objectContaining({}).asymmetricMatch(null), - objectContaining({}).asymmetricMatch(undefined), objectNotContaining({[foo]: 'foo'}).asymmetricMatch({[bar]: 'bar'}), objectNotContaining({foo: 'foo'}).asymmetricMatch({bar: 'bar'}), objectNotContaining({foo: 'foo'}).asymmetricMatch({foo: 'foox'}), diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index 8eaa17d85bef..a128863e0983 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -227,6 +227,7 @@ class ObjectContaining extends AsymmetricMatcher< } asymmetricMatch(other: any) { + // Ensures that the argument passed to the objectContaining method is an object if (typeof this.sample !== 'object') { throw new TypeError( `You must provide an object to ${this.toString()}, not '${typeof this @@ -234,6 +235,14 @@ class ObjectContaining extends AsymmetricMatcher< ); } + // Ensures that the argument passed to the expect function is an object + // This is necessary to avoid matching of non-object values + // Arrays are a special type of object, but having a valid match with a standard object + // does not make sense, hence we do a simple array check + if (typeof other !== 'object' || Array.isArray(other)) { + return false; + } + let result = true; const matcherContext = this.getMatcherContext();