How to test react components props (expect component to be called with)
Asked Answered
W

2

8

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.

Woadwaxen answered 18/11, 2021 at 11:46 Comment(1)
Please provide the component code you want to test.Monotint
P
16

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()
);
Pteropod answered 19/11, 2021 at 10:57 Comment(8)
Hi! Thank you very much for the input. Unfortunately it doesn't solve my problem. I get a similar error: ibb.co/JQ4JKXjWoadwaxen
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.Pteropod
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!Woadwaxen
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?Pteropod
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!Woadwaxen
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 😄Pteropod
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.Woadwaxen
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 👍Pteropod
W
3

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

Woadwaxen answered 6/9, 2022 at 10:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.