Mocks don’t bite! Mastering Mocking with the React Testing Library

Translation of the article was prepared on the eve of the start of the course JavaScript Test Automation


Mocks – don’t bite!

They are designed to help you create simpler, more reliable tests. In this series of articles, I will show you the patterns that I rely on when mocking (or “stubbing”) React components.

Here’s a good example of a component stub. I use jest.mockwhich we’ll look at in more detail below.

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

A typical React component stub shouldn’t look more complicated. Pay attention to a very simple stub value (div) and attribute data-testidwhich makes it very easy for us to find the rendered instance in the DOM. By convention, the test identifier used must match the component name. In this case it is PostContent

Before we look at how they are used, let’s first take a look at what mocks are and why you might even want to use them.

What is a mock?

In the JavaScript world, the term mock very widely used to refer to any simulated implementation or test double… Mocked implementations are simply values ​​that replace others in your production code while the tests are running. They try on the interface of the object being replaced, so the rest of your code works as if there was no replacement.

There are several different reasons why you might want to do this; we’ll look at them with examples.

If you are interested in learning more about mocked implementations, check out Martin Fowler’s book “Mocks are not stubs”

Jest and mocking

Jest has a function jest.mockwhich allows you to mock entire modules that you replace. In this tutorial, I focused on this feature, although there are other ways to replace objects in JavaScript.

In the book Mastering React Test-Driven Development I am using ES6 named module imports to create test doubles. This approach gives a little more flexibility, but looks a little more hackish.

На странице Jest про jest.mock it says that moki ensure your tests are fast and not brittle

While this is true, this is not the main reason I use mocks.
I use mocks because they help me keep my tests independent of each other.

To understand why this is the case, let’s look at an example.

Why moki?

Below is the listing of the component BlogPagewhich does two things: it extracts id from property url and then displays PostContent component with this id

const getPostIdFromUrl = url =>
  url.substr(url.lastIndexOf("/") + 1)

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

  const id = getPostIdFromUrl(url)

  return (
    <PostContent id={id} />
  )
}

Imagine you are writing tests for this component and all your tests are included in BlogPage.test.js, which is a single set of tests covering the components BlogPage and PostContent

At this stage, you don’t need mocks yet: we have not seen it yet PostContentbut given the size BlogPage, there really is no need to have two separate test suites as BlogPage Is in the whole just PostContent

Now imagine that u BlogPage, and at PostContent functionality is added. As a gifted developer, you add more and more features every day.

It becomes more difficult to maintain tests in working order. Each new test has a more complex setup, and the test suite starts eating up more of your time — a burden that now needs to be supported.
This is a common problem and I see it all the time in React codebases. Test suites in which even the smallest change will result in many tests failing.

One solution is to split test suites. We will leave BlogPage.test.js and create a new PostContent.test.jswhich should contain tests exclusively for PostContent… The basic idea is that any functions placed in PostContentmust be specified in PostContent.test.js, and any functions placed in BlogPage (e.g. parsing url) should be in BlogPage.test.js

Okay.

But what if rendering PostContent has side effects?

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>
};

Test suite in BlogPage.test.js must be aware of the side effects and be prepared to handle them. For example, he will have to keep ready fetch answer.

The dependency that we tried to avoid by splitting our test suites still exists.

The organization of our testing has certainly gotten better, but in the end, nothing happened to make our tests less fragile.
For this we need a stub (or mock) PostContent
And in the next part we’ll look at how to do this.

Is it really necessary?

By the way, a few words about moving from end-to-end testing to unit tests.

Having test doubles is a key indicator that you are writing unit tests.

Many experienced testers start new projects right away with unit tests (and mocks) because they know that as their codebase grows, they will face the issue of test instability.

Unit tests are usually much smaller than end-to-end tests. They can be so small that they often take no more than three or four lines of code. This makes them great candidates for social coding practices such as pair and ensemble programming.

Even when we do unit testing, test doubles are not always necessary – they are just another tool in your suite that you need to know when and how to apply.

In the next part we will cover the basic mocking techniques

Similar Posts

Leave a Reply

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