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):

  1. Mocky doesn’t bite! Mastering Mocking with the React Testing Library

  2. Basic mocking format for React components

  3. Checking children passed to a mock React component

  4. Testing multiple instances of the same mock component

  5. 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 TopFivePostsPagewhich, 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 TopFivePostsPageyou 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 clearMocksto 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.”

GET A DISCOUNT

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *