marchaos / jest-mock-extended

Type safe mocking extensions for Jest https://www.npmjs.com/package/jest-mock-extended
MIT License
828 stars 57 forks source link

Order of calling mock.calledWith() matters #77

Closed derek-pavao closed 1 year ago

derek-pavao commented 2 years ago

Unless I'm missing something, which is quite possible. The order in which you set up mocks with calledWith matters.

A simple example:

describe('A simple example', () => {
  let mockHttpClient: MockProxy<HttpClient>;

  beforeEach(() => {
    mockHttpClient = mock<HttpClient>();
  });

  it('will return the most generic mock value if it\'s defined first', async () => {
    mockHttpClient.get.calledWith(any()).mockResolvedValue('first');
    mockHttpClient.get.calledWith(anyString()).mockResolvedValue('second');
    mockHttpClient.get.calledWith('bar').mockResolvedValue('third');

    await expect(mockHttpClient.get('foo')).resolves.toBe('second');
  });
});

in the example above the mock returns 'first'. In this example it isn't that big of a deal, you could just reorder them and put the more specific ones ontop and the mock works as expected.

However, the use case where I think it becomes problematic is if you want to define the generic mockResolvedValue in a beforeEach for all tests, and then define more specific mockResolvedValues with in a specific test, this behavior will cause an issue in that scenario.

Here's an example of the more problematic use case for reference

describe('A simple example', () => {
  let mockHttpClient: MockProxy<HttpClient>;

  beforeEach(() => {
    mockHttpClient = mock<HttpClient>();
    mockHttpClient.get.calledWith(any()).mockResolvedValue('first');
  });

  it('will return the most generic mock value if it\'s defined first', async () => {
    mockHttpClient.get.calledWith(anyString()).mockResolvedValue('second');
    mockHttpClient.get.calledWith('bar').mockResolvedValue('third');
    await expect(mockHttpClient.get('foo')).resolves.toBe('second');
  });
});

Any insight or suggestions is appreciated. Also, love this library. It's super useful.

kostuyn commented 2 years ago

you can reset mock before initialized for every test

`it('will return the most generic mock value if it\'s defined first', async () => {
    mockReset(mockHttpClient);   // <--- reset mock conditions

    mockHttpClient.get.calledWith(any()).mockResolvedValue('first'); // <--- duplicate if need
    mockHttpClient.get.calledWith(anyString()).mockResolvedValue('second');
    mockHttpClient.get.calledWith('bar').mockResolvedValue('third');
    await expect(mockHttpClient.get('foo')).resolves.toBe('second');
  });`
derek-pavao commented 2 years ago

Maybe I'm missing something, but this kinda renders beforeEach useless in this scenario doesn't it? I had expected that calledWith would choose the most specific match based on some set of rules. Maybe that wasn't the original intent, but it seems useful.

kostuyn commented 2 years ago

For single test - yes, useless. In other case you can set default order and reorder them in concrete test case with mockReset(mockHttpClient)