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

Extended toEqual to allow custom equality matcher. #7352

Closed

Conversation

EthanRBrown
Copy link

Summary

The internal implementation of toEqual allows for custom equality matchers. Jest takes advantage of this currently for iterable equality, but it would be very useful for end users to have access to this same extensibility. In particular, my case is that I want to be able to combine the functionality of toBeCloseTo with toEqual. That is, I have test cases with large, deep objects with floating-point values I wish to compare approximately. Exposing the existing custom equality matcher functionality allows this and other useful extensions.

Test plan

All existing tests pass, and I added a unit test to demonstrate the effective use of custom equality matchers to do approximate numeric comparison. Specifically:

Example of undesirable failing test:

const expected = {s: 'foo', x: 3};
const actual = {s: 'foo', x: 2.9999999};
jestExpect(expected).toEqual(actual) // fails undesirably

Using custom numeric matchers to create passing test with approximate numeric comparison:

const customNumericMatcher = (a, b) => {
  if (typeof a !== 'number' || typeof b !== 'number') return undefined;
  expect(a).toBeCloseTo(b, 4);
  return true;
};
const expected = {s: 'foo', x: 3};
const actual = {s: 'foo', x: 2.9999999};
jestExpect(expected).toEqual(actual, [customNumericMatcher]);  // passes desirably

* Modified toEqual to permit array of custom equality matchers.
* Added unit test with apprixmate numeric matching.
* Updated expect API documentation.
@rickhanlonii
Copy link
Member

Hey @EthanRBrown thanks for contributing to Jest!

Is there a proposal issue for this or is this PR the proposal?

@EthanRBrown
Copy link
Author

@rickhanlonii Hi, RIck...I was a little confused about the process, I apologize. It's my first time contributing to Jest. I did my best to follow CONTRIBUTING.md, and created a proposal (#7351) but then saw that the CHANGELOG.md links all seemed to be referencing the PR, not the proposal, so I wasn't sure if I had done it right....

Anyway, I'm happy to follow whatever process, just new to it. I have a need, I have a contribution that will solve that, I hope other people find it useful, etc.

@thymikee
Copy link
Collaborator

thymikee commented Nov 9, 2018

Um, in this case I think the custom matcher would be better solution? You can create on locally or use it in every file, here's the docs on this: https://jestjs.io/docs/en/expect#expectextendmatchers

@EthanRBrown
Copy link
Author

@thymikee That was my first approach, but because Jest's expect doesn't expose the ability to provide a custom equality matcher for toEqual, that didn't work either, at least I couldn't find a way....

@rickhanlonii
Copy link
Member

rickhanlonii commented Nov 9, 2018

No sweat, we can discuss here!

Did you know that you can use the equals function inside of a custom matcher?

expect.extend({
  toEqual(received, expected) {
    const pass = this.equals(received, expected, [customNumericMatcher]);

    // return message
  },
});

@EthanRBrown
Copy link
Author

@rickhanlonii I was not aware...and I was looking for something like that, unsuccessful. This certainly solves my problem...happy to close this PR if you think this is an inappropriate extension of the Jest expect API.

@thymikee
Copy link
Collaborator

thymikee commented Nov 9, 2018

If there's something that could ease finding this information on our docs, please send a PR to the docs :)

@thymikee thymikee closed this Nov 9, 2018
@Asday
Copy link

Asday commented Jul 3, 2019

While that example is useful, @rickhanlonii, extending toEqual()'s functionality in such a manner is still incredibly verbose, unless I'm missing something:

expect.extend({
  toEqual(received, expected, extraMatchers = []) {
    const pass = this.equals(received, expected, extraMatchers)

    // Duplicated from jest.
    // https://github.com/facebook/jest/blob/f3dab7/packages/expect
    // /src/matchers.ts#L538-L569
    /* eslint-disable */
    const matcherName = 'toEqual'
    const options = {
      comment: 'deep equality',
      isNot: this.isNot,
      promise: this.promise,
    }
    const message = pass
      ? () =>
          matcherHint(matcherName, undefined, undefined, options) +
          '\n\n' +
          `Expected: ${printExpected(expected)}\n` +
          `Received: ${printReceived(received)}`
      : () => {
          const difference = diff(expected, received, {expand: this.expand});

          return (
            matcherHint(matcherName, undefined, undefined, options) +
            '\n\n' +
            (difference && difference.includes('- Expect')
              ? `Difference:\n\n${difference}`
              : `Expected: ${printExpected(expected)}\n` +
                `Received: ${printReceived(received)}`)
          );
        };

    return {
      actual: received,
      expected,
      message,
      name: matcherName,
      pass,
    }
    /* eslint-enable */
    // Duplication ends here.
  },
})

@NinjaDev06
Copy link

NinjaDev06 commented Jun 10, 2020

The proposing solution will be a lot more easier than implementing many extend methods in my case. I used MomentJs and I want to use the isSame method from the moment object as the equality matcher.

The Extend is not convenient because I have to implement all yours expect method that use equal like toEqual, toHaveBeenCalledWith, toHaveBeenNthCalledWith, etc. Instead, I could set a custom matcher for Moment object to use in all Jest equal call.

Code suggestion

expect.equalityTester([
  (a: moment.Moment, b: moment.Moment) => {
    if (moment.isMoment(a) && moment.isMoment(b)) {
      return a.isSame(b);
    }
    return undefined;
  },
});

@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 11, 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.

6 participants