Testing multiple instances of the same mock component
Hi, Habr. Recruitment of students for the course has started “JavaScript Test Automation”… In this regard, we invite everyone to visit a free demo lesson on the topic: “What a tester needs to know about JS”…
And now we are sharing with you the continuation of a series of useful translations.
Mock React Components with Testing Library (5 part series):
Mocky doesn’t bite! Mastering Mocking with the React Testing Library
Basic mocking format for React components
Checking children passed to a mock React component
Testing multiple instances of the same mock component
Stay out of trouble
This is part 4 of a series on testing React with mock components. In part two, we looked at the basic shape of component mocks. In part three, we added the ability to pass child components. Now we’re going to tackle the hardest part of the puzzle: dealing with multiple instances of the same mock.
All code examples for this post are available in the following overview.
dirv / mock-react-components
An example of using stub React components.
Let’s continue working with the new component TopFivePostsPage
which, perhaps unsurprisingly, displays the top five posts.
import { PostContent } from "./PostContent"
export const TopFivePostsPage = () => (
<ol>
<PostContent id="top1" />
<PostContent id="top2" />
<PostContent id="top3" />
<PostContent id="top4" />
<PostContent id="top5" />
</ol>
);
For testing we use queryAllByTestId
combined with matcher toHaveLength
…
describe("BlogPage", () => {
it("renders five PostContent components", () => {
render(<TopFivePostsPage />)
expect(screen.queryAllByTestId("PostContent"))
.toHaveLength(5)
})
})
And for our second test, we can use five wait operators, each with different property values.
it("constructs a PostContent for each top 5 entry", () => {
render(<TopFivePostsPage />)
expect(PostContent).toHaveBeenCalledWith(
{ id: "top1" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top2" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top3" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top4" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top5" }, expect.anything())
})
But there is something not quite right in this. We have not tested the render order. Matcher ToHaveBeenCalledWith
doesn’t care about order.
Instead, we can use .mock.calls
…
it("renders PostContent items in the right order", () => {
render(<TopFivePostsPage />)
const postContentIds = PostContent.mock.calls.map(
args => args[0].id)
expect(postContentIds).toEqual([
"top1", "top2", "top3", "top4", "top5"
])
})
If you try to run it after the first two tests for TopFivePostsPage
you will get a strange error that PostContent
actually called fifteen times! This is due to the fact that we need to clear the mock between each test.
We do this by adding the property clearMocks
to our Jest config. Here is my package.json
for comparison.
"jest": {
"transform": {
"^.+\.jsx?$": "babel-jest"
},
"setupFilesAfterEnv": ["./jest.setup.js"],
"clearMocks": true
}
Note that the last test actually makes the previous test unnecessary, so you can safely delete it.
When it’s not enough: mock identity instance
Sometimes you need more than this. For example, you need to check the passed children and you also have multiple instances. In this case, you can use one of the component properties to give a unique test ID for your component instance.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children, id }) => (
<div data-testid={`PostContent-${id}`}>
{children}
</div>
))
}))
Personally, I really don’t like it. This is difficult, and more difficult than I think. But it does exist, and sometimes it needs to be used.
Remember that mocks are there to speed up testing, and testing is there to speed up development. When mocks get too complex, you spend more time reading and maintaining them, so they slow you down. I’ll talk about this in more detail in the next part.
More lessons
What have we learned?
Use
queryAllByTestId
when testing multiple instances of a mock component.Use
.mock.calls
to check the ordering of calls or to test render properties.Use Jest’s config setting
clearMocks
to make sure your plugs are cleaned before each test.If all else fails, you can use properties in the render results to give unique values
data-testid
for each instance.Keep the mocks as simple as possible!
That’s all. In the final part, we’ll look at why mocks can get you in trouble and how to avoid them.
Stay out of trouble
This is the final installment in the React Component Mock series. We’ll end it with a short description and then look at some of the common challenges you will face.
All code examples for this post are available in the following overview.
dirv / mock-react-components
An example of how to use stub React components.
Mocks are an extremely complex test provisioning section. This is why some teachers don’t use it or teach it.
But mastering the mocks will give you additional weapons to deal with erratic, time-consuming tests.
So how can you be sure that you will be safe with mocks? Simple: stick to templates!
If you stick to the patterns I show you in this series of examples, you shouldn’t have a problem.
Start with a basic mock function that generates a div
with attached data-testid
… We looked at this in second part…
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(() => (
<div data-testid="PostContent" />
))
}))
You can also generate children if needed. This has been described in third part…
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children }) => (
<div data-testid="PostContent">{children}</div>
))
}))
If you really need it, you can use the property value to make it unique data-testids
… But this is often unnecessary complexity. It was in fourth part.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children, id }) => <div data-testid={`PostContent-${id}`}>{children}</div>)
}))
I don’t like to give advice on what to avoid: every technique has its place. But if I were to name anything to be careful with, I would say that it would be creating mock objects and, in particular, using the Jest function mockImplementation
…
Why? Well, the main reason for using mocks and stubs is to help build independent test solutions that won’t slow you down.
An important way to do this is to limit your code to a small amount of boilerplate. This is a bit like a set of coding standards, but at a higher level.
When you start building mock objects and complex mock implementations, you move away from that goal, because now there is logic in your tests: you cannot look at them and immediately know how they work. And any change in the code of the finished software product requires you to rethink the fictitious implementation code, before inevitably changing it.
What if none of the patterns fit your test solutions?
If you get stuck, the first question you should ask yourself is: How much testing is my finished software product code?
Because it is not mocks that hurt, but the finished software product code that is not structured for testing.
Improve testability of your code
The number one problem I see with React codebases is very large components that express many different ideas. Often, new features just pile on top of each other, rather than wasting time separating abstractions or finding a logical organizational structure.
So splitting up your larger components is a good place to start.
How big is the size? File size is often a good metric to use: anything larger than 100 lines is suspicious to me. And many of my components are less than 10 lines in size!
What if it’s not clear how to split the component? Start with the principle of single responsibility: each component should do only one thing and only one thing.
Of course, the concept of one “thing” leaves you with enough rope to hang yourself. Finding elegant “things” is a big challenge in software design.
If you are interested in this topic, then I would advise you to learn about bonding, chaining and conjugationThis is all about React components, even if you don’t often hear React teachers talk about them.
Where to go?
In this series, I’ve shown you a very specific way to test React components. If you are interested in a more detailed theory and history of these techniques, then take a look at my book “Mastering development based on testing React components“. It does not use the React Testing Library, but explores testing from first principles. This will give you a deeper understanding of what successful React testing is.
Learn more about the course “JavaScript Testing Automation”.
Sign up for an open webinar “What a tester needs to know about JS.”