Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest mock function arguments don't match when using immutable data structures

When trying to test the arguments passed to the function with Jest's .toHaveBeenCalledWith() method, the test fails if I am treating with immutable data structures using ImmutableJS library. The test fails with the message similar to this:

Expected mock function to have been called with:
  [{"foo": true, "bar": "baz"}]
But it was called with:
  [{"foo": true, "bar": "baz"}]

The test looks similar to this:

const expectedArgs = Map({
  foo: true,
  bar: 'baz'
});

const foo = jest.fn();
bar();
expect(foo).toHaveBeenCalledWith(expectedArgs);

And the function similar to this:

const bar = () => {
  const baz = Map({});
  const bazModified = baz.set('foo', true).set('bar', 'baz');
  foo(bazModified);
}

I realized that if I pass the arguments in this manner everything works fine:

const bar = () => {
  const baz = Map({
    foo: true,
    bar: 'baz'
  });
  foo(baz);
}

The problem is that this is a big simplification of my function's logic and I have to use .set to construct the object. Does anybody have an idea why the approach with .set is failing to evaluate correctly?

like image 661
Grzegorz Pawlik Avatar asked Dec 06 '16 20:12

Grzegorz Pawlik


People also ask

How do you mock a parameter in Jest?

There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.

What is the difference between Jest FN and Jest mock?

mock replaces one module with either just jest. fn , when you call it with only the path parameter, or with the returning value of the function you can give it as the second parameter.

Which method is useful to clean up a mock's usage data between two assertions?

mockFn.mockClear() ​ Often this is useful when you want to clean up a mocks usage data between two assertions.

Does Jest clear mocks between tests?

In order to run a piece of code before every test, Jest has a beforeEach hook, which we can use as follows. Clears the mock. calls and mock. instances properties of all mocks.


1 Answers

So your test is failing because toHaveBeenCalledWith only passes if the instances of the entities are exactly the same. It is similar to the following, which also fails:

const expectedArgs = Map({
  foo: true,
  bar: 'baz'
});

const input =  Map({
  foo: false,
  bar: 'baz'
});

const result = input.set('foo', true);

expect(result).toBe(expectedArgs);

This, on the other hand, does work:

expect(result).toEqual(expectedArgs);

Because that performs a deep equal.

There is no way you will be able to test equality with toHaveBeenCalledWith, because baz.set('foo', true) will always return a new instance (which is the point of using immutable data).

I don't think there is a way to make toHaveBeenCalledWith behave like toEqual, so I guess the way to go is to manually test the mock call with toEqual:

const expectedArgs = Map({
  foo: true,
  bar: 'baz'
});

const foo = jest.fn();
bar();
// foo.mock.calls[0][0] returns the first argument of the first call to foo
expect(foo.mock.calls[0][0]).toEqual(expectedArgs);

See the documentation on toBe, toEqual, and mock calls

like image 95
Vlemert Avatar answered Sep 28 '22 17:09

Vlemert