7

I need to test that a react component is called with opened={true} prop after an button click is fired. I am using testing-library ( testing-library/react + testing-library/jest-dom).

I mocked the Component using something like

import Component from "./path-to-file/component-name"
...
jest.mock("./path-to-file/component-name", () => {
  return jest.fn().mockImplementation(() => {
    return null
  })
})

I first tried with:

expect(Component).toBeCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenLastCalledWith(expect.objectContaining({"opened": true}))

but I got Error: expect(jest.fn()).toBeCalledWith(...expected).

Same went for expect.objectContaining({"opened": expect.anything()})

expect.objectContaining({"opened": expect.anything()})

And even for expect(Component).toBeCalledWith(expect.anything())

expect.anything()

And the difference is empty array: empty array

I also tried with expect(ChartMenu.mock).toBeCalledWith(expect.anything()). I got a different error but still not working (this time the error was Error: expect(received).toBeCalledWith(...expected) + Matcher error: received value must be a mock or spy function) mock

Thank you in advice!

EDIT: here is a simplified version of the component I want to test:

const Component = () => {
  const [chartMenuOpened, setChartMenuOpened] = useState(false)
  return (
      <Flex>
        <EllipseIcon onClick={() => setChartMenuOpened(true)}>
          +
        </EllipseIcon>
        <ChartMenu
          opened={chartMenuOpened}
          close={() => setChartMenuOpened(false)}
        />
      </Flex>
  )
}

Basically I want to make sure that when the + icon is clicked the menu will be opened (or called with open value). The issue is that I cannot render ChartMenu because it needs multiple props and redux state.

I was able in the end to mock useState in order to check that the setState was properly called from the icon component (in order to make sure there won't be future changes on the component that will break this using this post).

But I would still really appreciate an answer to the question: if there is any way to create a spy or something similar on a react component and check the props it was called with? Mostly because this was a rather simple example and I only have one state. But this might not always be the case. Or any good idea on how to properly test this kind if interaction would be really appeciated.

Berci
  • 2,876
  • 1
  • 18
  • 28

2 Answers2

11

I think you are on the right track to test if the component has been called with that prop, it's probably the syntax issue in your code

I learn this trick from colleague and you can try to see if this helps fix your issue.

expect(Component).toHaveBeenCalledWith(
  expect.objectContaining({
    opened: true,
  }),
  expect.anything()
);
He Wang
  • 647
  • 7
  • 19
  • Hi! Thank you very much for the input. Unfortunately it doesn't solve my problem. I get a similar error: https://ibb.co/JQ4JKXj – Berci Nov 19 '21 at 22:25
  • No worries, from your error message, I can see the reason it failed was that the `opened` was set to false though, so it might be related to other areas in your testings as well. – He Wang Nov 20 '21 at 11:25
  • Oh yes! You are right. I am stupid ( I was mocking the React.setState as a workaround, so it didn't really changed the state value). Anyway this works! Thank you very much! I would really appreciate an explanation if you have one, but either way your solution is valid so I'll accept it. Thank you! – Berci Nov 20 '21 at 17:10
  • So if you remove `expect.anything()` and let this test fail, then you will see what your component has been called with. There should be multiple arguments that called with `Component` in this case but those things aren't what you care, so you are loosely matching the single important thing `opened: true`, so you use `expect.anything` to match whatever random arguments the component was called with in your test. Does that make sense? – He Wang Nov 21 '21 at 23:28
  • I think it does. So the component is called basically with more than just 1 plain object and that is why it was failing? But what I still find weird is that `expect(Component).toBeCalledWith(expect.anything())` was failing. Anyway thank you! – Berci Nov 22 '21 at 11:41
  • All good, I think the reason `expect(Component).toBeCalledWith(expect.anything())` fails is because the component is called with more than 1 arugment, so if you can try `expect(Component).toBeCalledWith(expect.anything(), expect.anything())` to see if that work. This is purely my assumption though :) Glad I was helpful – He Wang Nov 23 '21 at 10:35
  • Just for the record you right again! And in case anyone else is wondering it seems like `expect(Component).toHaveBeenCalledWith( expect.objectContaining({opened: true}),{} )` is also working. So basically there is an extra empty object. – Berci Nov 24 '21 at 08:56
  • 1
    Correct, I tried `{}` as the second argument as well, but not sure why there is a second argument there, at least we know how it works now – He Wang Nov 24 '21 at 09:35
2

While the question on how to is answered, I did some extra research on this and it seems like in React components there is a second parameter refOrContext (so basically most of the time it's an empty object, but it can also be a ref or a context)

Despite pointing out the reason for the behavior, I also wanted to highlight that it is safer to use expect.anything() as the second argument (rather than just {} which would work only in most of the cases ):

More information about React second argument here

Berci
  • 2,876
  • 1
  • 18
  • 28