Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented the .toMatchObject(object) assertion #1843

Merged
merged 5 commits into from
Nov 28, 2016
Merged

Implemented the .toMatchObject(object) assertion #1843

merged 5 commits into from
Nov 28, 2016

Conversation

excitement-engineer
Copy link
Contributor

Summary

Implemented the toMatchObject(object) function that checks whether an object matches a subset of the properties of another object. This was discussed in issue #1793.

This matcher works for objects with a combination of key/value pairs, arrays and javascript dates:

const input1 = {a: 'b', t: {z: 'z', x: {r: 'r'}}};
const matcher1 = {a: 'b', t: {z: 'z'}};

const input2 = {a: new Date('2015-11-30'), b: 'b'};
const matcher2 =  {a: new Date('2015-11-30')};

const input3 = {a: [3, 4, 5], b: 'b'};
const matcher3 =  {a: [3, 4]};

//these tests pass now
expect(input1).toMatchObject(matcher1);
expect(input2).toMatchObject(matcher2);
expect(input3).toMatchObject(matcher3);

@facebook-github-bot
Copy link
Contributor

Thank you for your pull request. As you may know, we require contributors to sign our Contributor License Agreement, and we don't seem to have you on file and listed as active anymore. In order for us to review and merge your code, please email cla@fb.com with your details so we can update your status.

@codecov-io
Copy link

codecov-io commented Oct 2, 2016

Current coverage is 88.95% (diff: 98.27%)

Merging #1843 into master will increase coverage by 0.38%

@@             master      #1843   diff @@
==========================================
  Files            39         39          
  Lines          1409       1467    +58   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1248       1305    +57   
- Misses          161        162     +1   
  Partials          0          0          

Powered by Codecov. Last update 4e9e4df...5becc2e

@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@quantizor
Copy link
Contributor

Can this be the default for .toHaveBeenCalledWith when comparing objects, @cpojer?

@cpojer
Copy link
Member

cpojer commented Oct 4, 2016

No, I would prefer toBeCalledWith etc. to stay strict.

@excitement-engineer
Copy link
Contributor Author

Any updates on the status of this PR?

@cpojer
Copy link
Member

cpojer commented Nov 7, 2016

@excitement-engineer would you mind rebasing this diff?

@excitement-engineer
Copy link
Contributor Author

@cpojer I have performed the rebase:)

@cpojer
Copy link
Member

cpojer commented Nov 8, 2016

Ok thanks! @DmitriiAbramov is going to handle this whenever he gets around to it :)

@excitement-engineer
Copy link
Contributor Author

Great! Thanks a lot for your help!

[{a: 'b', t: {z: 'z', x: {r: 'r'}}}, {a: 'b', t: {z: 'z'}}],
[{a: 'b', t: {z: 'z', x: {r: 'r'}}}, {t: {x: {r: 'r'}}}],
[{a: [3, 4, 5], b: 'b'}, {a: [3, 4, 5]}],
[{a: [3, 4, 5], b: 'b'}, {a: [3, 4]}],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm a little scared to have the same behavior on arrays.
Properties definitely make sense, but for arrays it can be dangerous if you match some enums or other sets.

for example, this will pass, and it's pretty straightforward:

expect({permissions: ['read', 'write'], ...manyOtherProps}).toMathcObject({permissions: ['read']});

but if the left side is a return of the function, that might be not intuitive and even confusing:

expect(getPermissions()).toMathcObject({permissions: ['read']});
// i expect it to be just 'read', but there's and extra value that i don't even see, and the test is passing

should we match only objects, and use strict equality for arrays? what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you! I have removed this behaviour and have added a check that an equality check is performed for the elements in the array.

So this test will now fail:

expect({permissions: ['read', 'write'], ...manyOtherProps}).toMatchObject({permissions: ['read']});

There is one exception with the equality check mentioned above; you can still perform matchObject checks for key-value objects contained in arrays (i.e. array of objects), so in my update this will pass:

expect([{a: 'a', b: 'b'}, 5]).toMatchObject([{a: 'a'}, 5])

Do you agree with this behaviour?

if (typeof receivedObject !== 'object' || receivedObject === null) {
throw new Error(
matcherHint('[.not].toMatch', 'object', 'expected') + '\n\n' +
`${RECEIVED_COLOR('string')} value must be an object.\n` +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now it prints
screen shot 2016-11-15 at 9 33 02 pm

you probably copied 'string' from .toMatch matcher :)

i'm trying to think of a good message.

Expect object to be an object. Received

or

Expect expected to be an object. Received:

look king of weird.

maybe something like:

<red>object</red> value must be an object. Received:

and

<green>expected</green> value must be an object. Received:

thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, thanks for catching that, haha a bit embarrassing that I didn't see this;)

I implemented your suggestion:

<green>expected</green> value must be an object. Received:

const message = pass
? () => matcherHint('.not.toMatchObject') +
`\n\nExpected value not to match object:\n` +
` ${printExpected(JSON.stringify(expectedObject))}` +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't really use JSON.stringify here.
you should use printExpected and printReceived, they won't break on circular references and will handle other special values like undefined and friends :)

i think what we should also print is a diff between the entire expected and filtered received object, with only properties that matter. something like:

expect({a: 1, b: 1, c: 1, d: {e: {f: 555}}}).toMatch({d: {e: {f: 222}}});

Expected object to match:
  {d: {e: {f: 222}}}
Received:
  {a: 1, b: 1, c: 1, d: {e: {f: 555}}}
Difference:
  {
     d: {
       e: {
+         f:  555,
-          f: 222
        }
     }
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip, I have changed the code!

Any tips on how I can implement this diffing? Is it an idea to update the jest-diff package with a new DiffOption?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have an idea. I will delete all the properties that are contained in the Received object but not contained in the matching object. I can then just use jest-diff without introducing any changes there! Expect an update with this soon!

@excitement-engineer
Copy link
Contributor Author

@DmitriiAbramov I have made all the changes that you suggested in your code review. Let me know what you think!

@aaronabramov
Copy link
Contributor

this PR is awesome! thank you so much @excitement-engineer!
and sorry for the long response. holiday season in the US :)

@aaronabramov aaronabramov merged commit 8188ab9 into jestjs:master Nov 28, 2016
@excitement-engineer
Copy link
Contributor Author

Thanks a lot for you help! No worries about the timing:)

@chodorowicz
Copy link

chodorowicz commented Dec 13, 2016

Guys, just to let you know: toMatchObject documentation is already official and live but the code is not present in the most recent release v17.0.3 so we're getting TypeError: expect(...).toMatchObject is not a function.

https://facebook.github.io/jest/docs/api.html#tomatchobjectobject
https://github.com/facebook/jest/blob/v17.0.3/packages/jest-matchers/src/matchers.js#L479

@thymikee
Copy link
Collaborator

Yea, this was already reported in #2195 :D Thanks!

tushardhole pushed a commit to tushardhole/jest that referenced this pull request Aug 21, 2017
* Implemented .toMatchObject()

* Fixed error message and array equality in toMatchObject()

* Fixed listing errors.

* toMatchObject now displays a diff when a test fails
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 14, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants