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.
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 thejest-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.