Loose match one value in jest.toHaveBeenCalledWith

Jestjs

Jestjs Problem Overview


I have an analytics tracker that will only call after 1 second and with an object where the intervalInMilliseconds (duration) value is not deterministic.

How can I use jest.toHaveBeenCalledWith to test the object?

 test('pageStats - publicationPage (will wait 1000ms)', done => {
  const track = jest.fn()

  const expected = new PayloadTiming({
    category: 'PublicationPage',
    action: 'PublicationPage',
    name: 'n/a',
    label: '7',
    intervalInMilliseconds: 1000 // or around
  })

  mockInstance.viewState.layoutMode = PSPDFKit.LayoutMode.SINGLE
  const sendPageStats = pageStats({
    instance: mockInstance,
    track,
    remoteId: nappConfig.remoteId
  })

  mockInstance.addEventListener('viewState.currentPageIndex.change', sendPageStats)

  setTimeout(() => {
    mockInstance.fire('viewState.currentPageIndex.change', 2)

    expect(track).toHaveBeenCalled()
    expect(track).toHaveBeenCalledWith(expected)

    done()
  }, 1000)

  expect(track).not.toHaveBeenCalled()
})

expect(track).toHaveBeenCalledWith(expected) fails with:

Expected mock function to have been called with:
      {"action": "PublicationPage", "category": "PublicationPage", "intervalInMilliseconds": 1000, "label": "7", "name": "n/a"}
    as argument 1, but it was called with
      {"action": "PublicationPage", "category": "PublicationPage", "intervalInMilliseconds": 1001, "label": "7", "name": "n/a"}

I have looked at jest-extended but I do not see anything useful for my use-case.

Jestjs Solutions


Solution 1 - Jestjs

This can be done with asymmetric matchers (introduced in Jest 18)

expect(track).toHaveBeenCalledWith(
  expect.objectContaining({
   "action": "PublicationPage", 
   "category": "PublicationPage", 
   "label": "7",
   "name": "n/a"
  })
)

If you use jest-extended you can do something like

expect(track).toHaveBeenCalledWith(
  expect.objectContaining({
   "action": "PublicationPage", 
   "category": "PublicationPage", 
   "label": "7",
   "name": "n/a",
   "intervalInMilliseconds": expect.toBeWithin(999, 1002)
  })
)

Solution 2 - Jestjs

You can access the expected object for a better assertion using track.mock.calls[0][0] (the first [0] is the invocation number, and the second [0] is the argument number). Then you could use toMatchObject to find partially match the object, avoiding the dynamic parameters such as intervalInMilliseconds.

Solution 3 - Jestjs

To re-iterate the comment by cl0udw4lk3r as I found this the most useful in my scenario:

If you have a method that accepts multiple parameters (not an object) and you only want to match some of these parameters then you can use the expect object.

Example

method I want to test:

client.setex(key, ttl, JSON.stringify(obj));

I want to ensure the correct values are passed into the key and ttl but I'm not concerned what the object passed in is. So I set up a spy:

const setexSpy = jest.spyOn(mockClient, "setex");

and I can then expect this scenario thus:

expect(setexSpy).toHaveBeenCalledWith('test', 99, expect.anything());

You can also use more strongly typed calls using expect.any (expect.any(Number)) etc.

Solution 4 - Jestjs

Of course I'm biased but I think this is the best and cleanest way. You can use the spread operator ... to expand the object you are checking then overwrite one or more values.

Here is an example showing how to overwrite the "intervalInMilliseconds" expected value to any Number

const track = jest.fn()

const expected = new PayloadTiming({
    category: 'PublicationPage',
    action: 'PublicationPage',
    name: 'n/a',
    label: '7',
    intervalInMilliseconds: 1000 // or around
  })

expect(track).toHaveBeenCalledWith(
  {
    ...expected, 
    intervalInMilliseconds: expect.any(Number)
  })

another example showing how to overwrite two values

expect(track).toHaveBeenCalledWith(
  {
    ...expected, 
    intervalInMilliseconds: expect.any(Number), 
    category: expect.any(String)
  })

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondotnetCarpenterView Question on Stackoverflow
Solution 1 - JestjsRoman UsherenkoView Answer on Stackoverflow
Solution 2 - JestjsHerman StarikovView Answer on Stackoverflow
Solution 3 - JestjsLiamView Answer on Stackoverflow
Solution 4 - JestjsNathan Clark BaumgartnerView Answer on Stackoverflow