Jest: Immutable.js equality

Created on 15 May 2017  路  3Comments  路  Source: facebook/jest

Do you want to request a feature or report a bug?

Feature

What is the current behavior?

The following test will unexpectedly fail because Immutable.js sometimes creates a new reference, although the value is the same. Look at this test for example:

it('works with immutable', () => {
  const directlyCreated = new Immutable.Map([['foo', 'bar']]);
  const indirectlyCreated = (new Immutable.Map()).set('foo', 'bar');
  const fn = jest.fn();
  fn(directlyCreated, indirectlyCreated);

  expect(fn).toHaveBeenCalledWith(indirectlyCreated, directlyCreated);

  expect(() =>
    expect(fn).not.toHaveBeenCalledWith(indirectlyCreated, directlyCreated),
  ).toThrowError();
})

Although directlyCreated and indirectlyCreated have the same content and are treated the same by Immutable.js, the reference of the objects differ since one is created directly and another one is created by modifying an empty map. This errors with a very strange message:

Expected spy to have been last called with:
  [Immutable.Map {foo: "bar"}, Immutable.Map {foo: "bar"}]
But it was last called with:
  [Immutable.Map {foo: "bar"}, Immutable.Map {foo: "bar"}]

What is the expected behavior?

Our matches should be aware of Immutable.js objects and use Immutable.is or xyz#equals() in that case.

Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.

This happens in the current master version and happens because the eq method taken from jasmine does not support Immutable.is for comparison. Now that we control those matchers, we might add support for that.

If this is something we want to support, I'd love to work on it!

Edit: I run into this a lot and whenever I do, I have to use fn.mock.calls[0][0] to do the comparison, which, when using the toEqual matcher, strangely works.

Edit2: Can it be that the matchers for toHaveBeenCalledWith compare the array of arguments using equals() and since this is a regular type, it uses a different code path then, when equals() is called with the Immutable.js type directly?

Most helpful comment

What about creating a node module supporting that first? As soon as it gets stable enough we could merge it into Jest

All 3 comments

@philipp-spiess I guess we can add custom matchers like toHaveBeenCalledWithImmutable(map1, map2).

\\ Pretty hacky way to do it
expect.extend({
  toHaveBeenCalledWithImmutable(actualFn, expected) {
    const actual = actualFn.mock.calls
    const pass = actual.every((actualCall, callIndex) => {
      return actualCall.every((actualParam, paramIndex) => {
        return actualParam.equals ? actualParam.equals(expected[callIndex][paramIndex]) : actualParam === expected[callIndex][paramIndex]
      })
    })
    const message = pass
      ? `expected ${actual} to be not equal to ${expected}`
      : `expected ${actual} to be equal to ${expected}`;

    return {message, pass};
  }
})

I guess we can have extensions to jest matchers. If jest supports .equals that would be awesome 馃憤

What about creating a node module supporting that first? As soon as it gets stable enough we could merge it into Jest

There is already this project: https://github.com/unindented/custom-immutable-matchers

It could be just extended with toHaveBeenCalledWithImmutable

Was this page helpful?
0 / 5 - 0 ratings