Basic mocking format for React components
On the eve of the start of the course JavaScript Test Automation we continue to publish the translation of a series of useful articles
In the first part of this series, we looked at why mocks are actually useful.
In this part I will cover the basic format of React component poppies.
All code samples for this article are available in this repository.
Examples of mocking React components.
Let’s take another look at the components we are working with: BlogPage
and PostContent
…
Here BlogPage
:
const getPostIdFromUrl = url =>
url.substr(url.lastIndexOf("https://habr.com/") + 1)
export const BlogPage = ({ url }) => {
const id = getPostIdFromUrl(url)
return (
<PostContent id={id} />
)
}
BlogPage
doesn’t do much other than display PostContent
… But it has some functionality that interests us, namely parsing a props url
for getting id
messages.
PostContent
a little more complicated: it calls a function built into the browser fetch
to get the text of a blog post by url /post?id=${id}
, where id is the prop passed to it.
export const PostContent = ({ id }) => {
const [ text, setText ] = useState("")
useEffect(() => {
fetchPostContent(id)
}, [id])
const fetchPostContent = async () => {
const result = await fetch(/post?id=${id})
if (result.ok) {
setText(await result.text())
}
}
return <p>{text}</p>
}
In fact, what does PostContent
, it doesn’t matter, because we will not see him again!
We are going to write some tests for BlogPage
in our test file BlogPage.test.js
… For this we will create a mock PostContent
so as not to worry about its implementation.
The important point is that we completely drown out PostContent
so that our test suite BlogPage.test.js
was protected from everything that does PostContent
…
Here is a mock for PostContent
:
import { PostContent } from "../src/PostContent"
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(() => (
<div data-testid="PostContent" />
))
}))
Let’s break it down.
Mock is determined using
jest.mock
… The corresponding imports should be reflected here. The call is suspended toimport
could be replaced. Jest replaces the entire module with your newly defined module. So, in this case, we wet the whole../src/PostContent
file.
Since mocks are at the module level, any component you mock must be in a separate module.
Call
jest.fn
creates a spy: an object that records when it was called and with what parameters. Then we can check the calls using matcherstoHaveBeenCalled
andtoHaveBeenCalledWith
…
Parameter
jest.fn
defines a stub value that is returned when the function is called (when the component is rendered).
Stub implementations should always be as simple as possible. For React components this means
div
– HTML element with, perhaps, the least amount of semantic load!
Have him there is an attribute
data-testid
which we’ll use to get this particular element into the DOM.
React Testing Library opposes using
data-testid
where possible, because she wants you to treat your testing as if the tester were a real person using your software. But for mocks, I ignore this prescription because mocks are by definition a technical problem.
Value
data-testid
matches the name of the component. In this case it isPostContent
… This is the standard convention I follow for all my mocks.
This is the basic format for mocking React components. 90% (or more) of my mocks look like this. The remaining 10% have small additions, which we will look at in future articles.
Now that we have this mock, let’s write some tests for BlogPage
…
Checking that a component’s mock is displayed in the DOM
describe("BlogPage", () => {
it("renders a PostContent", () => {
render(<BlogPage url="http://example.com/blog/my-web-page" />)
expect(screen.queryByTestId("PostContent"))
.toBeInTheDocument()
})
})
This test is the first of two tests that is always required when using component mocks. screen.queryByTestId
searches the current DOM for a component with a value data-testid
appropriate PostContent
…
In other words, it checks if we actually rendered PostContent
component.
Responsible use of queryByTestId
Please note that I used queryByTestId
… The React Testing Library tries to insulate you from this feature for two reasons: First, it wants you to use getBy
instead queryBy
secondly, as I mentioned above, it does not welcome searches by test ID.
In fact, mocking testing is the only time I use queryByTestId
… I cannot remember a case where I could not have avoided using TestId
for non-wet components. But for mocks, this is ideal: because this is exactly the technical detail that we want to test. The user will never see this component, it is exclusively for our tests.
We get the ability to have a consistent way of creating mock objects: <div data-testid="ComponentName" />
is a standard template that we can use for all mock objects.
getBy * vs. queryBy *
Options getBy
throw exceptions if they cannot find a match for the element. In my opinion, this is only relevant when the calls not are part of the expectation.
So, if you have:
expect(screen.getByTestId("PostContent"))
.toBeInTheDocument()
If you hadn’t visualized <PostContent />
, this test would fail with an exception from getByTestId. Expectations never come true!
Given the choice between not fulfilling the expectation or throwing an exception, I will always choose the wait because it is more meaningful to the tester.
Unit tests, and in particular TDD (Test Driven Development) style tests, are very often associated with the presence of elements. For these tests I prefer queryBy
…
Verifying that the mock passed the correct props
The second test we need is checking if the correct props have been passed to PostContent
…
it("constructs a PostContent with an id prop created from the url", () => {
const postId = "my-amazing-post"
render(<BlogPage url={http://example.com/blog/${postId}} />)
expect(PostContent).toHaveBeenCalledWith(
{ id: postId },
expect.anything())
})
This is where the standard Jest matchers are used, toHaveBeenCalledWith
to ensure that the function PostContent
was called with the parameters we expected.
When React instantiates your component, it just calls a specific function with props as an object in the first parameter and ref in the second parameter. The second parameter is usually not important.
JSX operator <PostContent id="my-amazing-post" />
results in a function call PostContent ({id: "my-amazing-post"})
…
However, it also includes a phantom second parameter, which we will never need, but we have to take it into account.
Using expect.anything for the second parameter toHaveBeenCalledWith
The second parameter that React passes to your component is an instance reference (ref). This usually doesn’t matter for our tests, so you always need to pass expect.anything()
to indicate that you are not interested in its meaning.
If you want to get rid of the call expect.anything()
, you can write your own Jest mapper that handles it for you.
If you are not passing props, just use toHaveBeenCalled
In rare cases, the component you are mocking does not take any parameters. you can use toHaveBeenCalled
as a simplified version toHaveBeenCalledWith
…
Understanding the basic rules for mocking components
We wrote two tests and one mock. Here are the important lessons we’ve learned so far:
Your mock should be spy using jest.fn and have the value returned from the stub of the simplest component you can have, namely
<div />
…
You should also install
data-testid
so that you can directly define that element in the DOM.
By convention, the value of this attribute is the name of the component to be mocked. So for the component
PostContent
its damped value is<div data-testid = "PostContent" />
…
Each mock requires at least two tests: the first checks to see if it is present in the DOM, and the second checks that it was called with the correct props.
Why do we need these two tests?
I’ve already mentioned a couple of times that we need at least two tests. But why?
If you didn’t have the first test to check for presence in the DOM, then you could pass the second test using a simple function call:
export const BlogPost = () => {
PostContent({ id: "my-awesome-post" })
return null
}
Why do you need this – the topic of a whole separate article. Here’s a short version: we usually think of a function call easier, than the JSX operator. When you use strict testing principles, you should is always write the simplest code to make the test pass.
How about writing only the first test and scoring the second?
You can make it passable like this:
export const BlogPost = () => (
<PostContent />
)
Again, this is the simplest production code to pass the test.
You will need both tests to get to the actual solution.
This is an important difference between end-to-end tests and unit tests: unit tests are defensive as opposed to end-to-end tests.
Key point: Always write the simplest production code to make your tests pass. This will help you write a test suite that covers all scenarios.
That’s all for the basic format of component mocks. In the next part we will look at testing child components that are passed to your mocks…
We invite you to sign up for free demo lesson on the topic: “Puppeteer Basics”…
Read more:
Mocks don’t bite! Mastering Mocking with the React Testing Library