Mocky doesn't bite! Mastering Mocking with the React Testing Library

The translation of the article was prepared in anticipation of the start of the course "Test Automation in JavaScript" .










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.mock, which 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. Note the very simple stub value ( div) and an attribute data-testidthat makes it very easy for us to find a 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 remember what mocks are and why you might even want to use them.



What is a mock?



In the JavaScript world, the term mock is very widely used to refer to any mocked 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, read Martin Fowler 's book Mocks Are Not Stubs .



Jest and mocking



Jest has a feature jest.mockthat lets you 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 use ES6 named module imports to create test doubles. This approach gives a little more flexibility, but looks a little more hackish.



Jest jest.mockit says mocks 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 a listing of a component BlogPagethat does two things: it fetches idfrom a property urland then displays a PostContentcomponent with that id.



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

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

  const id = getPostIdFromUrl(url)

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




Imagine that you are writing tests for this component, and all your tests are included in BlogPage.test.js, which is a single test suite that covers components BlogPageand PostContent.



At this point, you don't need the mocks yet: we haven't seen PostContentit yet, but given the size BlogPage, there really isn't a need to have two separate test suites, since BlogPageit's generally simple PostContent.



Now imagine adding functionality to BlogPageboth y and y PostContent. 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.jsand create a new PostContent.test.jsone that should contain tests exclusively for PostContent. The basic idea is that any functions placed in PostContentmust be in PostContent.test.js, and any functions placed in BlogPage(such as URL parsing) must be in BlogPage.test.js.



Okay.



But what if renderingPostContent 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>
};




The test suite BlogPage.test.jsmust be aware of the side effects and be prepared to handle them. For example, he will have to keep an fetchanswer ready .



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 seasoned testers start new projects right away with unit tests (and mocks) because they know that as their codebase grows, they will run into test instability issues.

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 .






All Articles