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.

dirv / mocking-react-components

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 PostContentso as not to worry about its implementation.

The important point is that we completely drown out PostContentso 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 to import 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 matchers toHaveBeenCalled and toHaveBeenCalledWith

  • 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-testidwhich 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 is PostContent… 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 queryBysecondly, 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, toHaveBeenCalledWithto 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-testidso 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

Similar Posts

Leave a Reply

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