DEV Community

loading...

Verifying children passed to React mock components

d_ir profile image Daniel Irvine 🏳️‍🌈 ・3 min read

This is the third part in a series on React testing. In the last part, we looked at the basic form of React component mocks.

Another thing you might want to do with mocks is test that it has the correct children passed. That’s what we’ll look at now.


All the code samples for this post are available at the following repo.

GitHub logo dirv / mocking-react-components

An example of how to mock React components


Imagine that we want to inset a mailing list sign up form inside of the PostContent. We can do that by passing children elements to it.

Here’s the newly improved BlogPage component:

export const BlogPage = ({ url }) => {

  const id = getPostIdFromUrl(url)

  const handleSignUp = () => {
    // ...
  }

  return (
    <PostContent id={id}>
      <input type="email" placeholder="Sign up to my mailing list!" />
      <button onClick={handleSignUp}>Sign up</button>
    </PostContent>
  )
}

Crucially, our BlogPage tests shouldn’t care what PostContent does with the children. They should just care that it was given the children.

We could test this by pulling out the children prop from the .mock.calls entry and then rendering it with render. In other words, treating it like a render prop.

But there’s a more straightforward way, which is to modify the mock component to render its children:

jest.mock("../src/PostContent", () => ({
  PostContent: jest.fn(({ children }) => (
    <div data-testid="PostContent">{children}</div>
  ))
}))

Now we can write a test that checks that a button was rendered as a child of PostContent:

it("renders the mailing list sign up button as a child of PostContent", () => {
  render(<BlogPage url="http://example.com/blog/my-web-page" />)

  const postContentElement = screen.getByTestId("PostContent")

  const button = screen.queryByRole(
    "button", { name: "Sign up" })

  expect(postContentElement).toContainElement(button)
})

The same technique can be repeated for the input field.

If you run this test, you’ll notice a problem. Our previous test that checks the passed props is now failing. Its expectation looked like this:

  expect(PostContent).toHaveBeenCalledWith(
    { id: postId },
    expect.anything())

It’s failing because all of a sudden we have a children prop, which is unexpected according to this test.

We fix that using expect.objectContaining.

Use expect.objectContaining to narrow down your tests


It’s often useful to have multiple unit tests for a single mock component call! I usually start with one test, with all props specified. But for any prop values of sufficient complexity, it can be useful to split that out into a test of its own with a good test description. The children prop is a special case of that: our test that checks we pass the right ID is independent of anything to do with the displaying inset content.


We can avoid testing content by using expect.objectContaining in our expectation:

  expect(PostContent).toHaveBeenCalledWith(
    expect.objectContaining({ id: postId }),
    expect.anything())

More lessons

So what have we learned now?

  • To test children passed to mocks, modify the mock component to be `jest.fn(({ children }) = {children})
  • Use toContainElement from the jest-dom matchers package to check that components are rendered as children of your mocked component.
  • Use expect.objectContaining to write unit tests that don’t break when your props change.
  • Use Jest’s clearMocks configuration setting to ensure your spies are cleared before each test.

In part 4, we’ll see how we go about testing multiple rendered instances of the same mock component.

Discussion

pic
Editor guide