React-testing-library: How to check if a component contains instance of another component?

Created on 19 Dec 2018  路  11Comments  路  Source: testing-library/react-testing-library

I'm wondering what's the best practice to check whether my testing component contains one or multiple instances of another component. The ideal scenario looks something like this:

// Component
export const AppHeader = () => (
  <nav data-testid="app-header">
    <Clock />
  </nav>
);

// Component.test.js
test('AppHeader renders a <Clock />', () => {
  const { getByTestId } = render(<AppHeader />);
  expect(getByTestId('app-header')).toContainInstanceOf(<Clock />);
});

Snapshot testing obviously doesn't work because the value of Clock keeps changing whenever we render AppHeader. Another potential solution is to set data-testid for my Clock component but in that case, how can we test the exact number of Clock rendered by AppHeader? With a clock ID, it'd require to pass data-testid as props into Clock because data-testid doesn't work on custom components.

help wanted question

Most helpful comment

Whatever ends up happening here, we're not going to implement anything within react-testing-library to assert whether certain components were rendered or how many times they were rendered. That's an implementation detail.

Thanks for the tips from people though!

All 11 comments

With a clock ID, it'd require to pass data-testid as props into Clock because data-testid doesn't work on custom components.

It is possible to assign the top level element a test-id though. One example could be:

const Clock = props => (
  <time data-testid={props["data-testid"]}>
      {/* clock code */}
  </time>
)
// Component
export const AppHeader = () => (
  <nav data-testid="app-header">
    <Clock data-testid="clock" />
    <Clock data-testid="clock" />
    <Clock data-testid="clock" />
  </nav>
);

// Component.test.js
test('AppHeader renders a <Clock />', () => {
  const { getAllByTestId, getByTestId } = render(<AppHeader />);
  const appHeader = getByTestId('app-header')
  const clocksInHeader = within(appHeader).getAllByTestId('clock')
  expect(clocksInHeader.length).toBe(3);
});

Live Example

Docs:

If the component is third-party and you can't modify it to accept a prop, you can use a Jest mock to wrap or replace the component:

// AppHeader.js
import Clock from 'clock';

export default function AppHeader () {
  return (
    <div>
      <Something />
      <Clock />
    </div>
  );
}
// AppHeader.test.js
import {within, render} from 'react-testing-library';
import Clock from 'clock';
const ActualClock = jest.requireActual('clock');
jest.mock('clock', () =>
  <div data-testid="clock">
    <ActualClock />
  </div>
);

test('AppHeader renders a <Clock />', () => {
  const { getAllByTestId, getByTestId } = render(<AppHeader />);
  const appHeader = getByTestId('app-header');
  const clocksInHeader = within(appHeader).getAllByTestId('clock');
  expect(clocksInHeader.length).toBe(3);
});

Using requireActual is optional; if you don't care what Clock renders you can just mock it with a div containing a testid.

The downside of using mocks instead of adding testids to components in your actual app is that if you decide to switch to a different clock component, or replace it with an inline helper component, the test may break unnecessarily; i.e., if the test is saying "there should be 3 clocks of any kind", using testids in the app code lets the app decide what component represents a clock, whereas mocking is tied to that specific module. Enzyme-style find(MyComponent) has the same problem.

Enzyme have more specific API to check how many times children component rendered into DOM. With react-testing-library I'm not sure Is there any kind API exist for covering this use case. For this use case, I always mock react component and pass through props.

Header.jsx

import React, { Component } from 'react'

class Header extends Component {
  render() {
    const { Clock } = this.props
    return (
      <div>
        <Clock />
        <Clock />
        <Clock />
      </div>
    )
  }
}

Clock.jsx

import React from 'react'

const Clock = (props) => <div>real clock time: {Date.now()}</div>

export default Clock

Header.test.js

import React from 'react'
import { render, cleanup } from 'react-testing-library'
import 'jest-dom/extend-expect'
import Header from '../Header'

afterAll(cleanup)

test('should render clock twice', () => {
  const mockClock = jest.fn(() => <div>mocked clock time: {Date.now()}</div>)
  // eslint-disable-next-line no-unused-vars
  const { debug } = render(<Header Clock={mockClock} />)
  expect(mockClock).toBeCalledTimes(3)
})

Whatever ends up happening here, we're not going to implement anything within react-testing-library to assert whether certain components were rendered or how many times they were rendered. That's an implementation detail.

Thanks for the tips from people though!

I'm totally agree 馃憤 with @kentcdodds. Don't write tests for component implementation details.

Whatever ends up happening here, we're not going to implement anything within react-testing-library to assert whether certain components were rendered or how many times they were rendered. That's an implementation detail.

Thanks for the tips from people though!

Can you explain how is testing whether a component was rendered on an implementation detail? If the user clicks a button and its job is to render a popup, shouldn't I be testing that the popup was rendered? Especially since its something the end user will be seeing on screen.

The user can't tell there is a React component named "Popup", just the HTML output of it

The user can't tell, but I do, and when I use TDD, I don't even know what the "Popup" component will contain as it's not even written yet. I think there should be something for this.

You can mock it with jest.mock if that's what your want to do

You can mock it with jest.mock if that's what your want to do

@kentcdodds Hi. How can i test my components with full coverage (100% in codecov by example)?

Other complexity: mocking of components that are used as children causes the tests not to fail when the component implementation changes.

Other question: how can i check that my component is using the child component correctly?

Example

import React from 'react';
import SomeButton from 'third-party-components';

const MyComponent = ({ onClick }) => (
  <div>
    <SomeButton 
      onClick={event => {
        console.log('some effect here...');
        onClick(event); // just call callback
      }}
    >Say hello!</SomeButton>
  </div>
);

I need to check that the spy function passes to onClick is called exactly when someone clicked on SomeButton.

But I can't just find the button in the tree and cause it to click, for example because the component is implemented like this:

export const SomeButton = ({ children, onClick }) => {
  const ref = useRef();

  useEffect(() => {
    // <SomeButton /> handles clicks of some other element on page (with dynamic query)
    const targetElement = document.body.querySelector(`.item[data-id=${Math.random()}]`);

    targetElement.addEventListener('click', onClick);

    return () => targetElement.removeEventListener('click', onClick);
  }, [onClick]);

  return (
    <button ref={ref}>{children}</button>
  );
};

SomeButton component encapsulates information about which element will actually be listened to clicks

In this case I don't need to test the markup. I need to test how my component interacts with the environment via a channel provided by a third party component.

Writing mocks every time for such cases is a rather laborious task. Essentially, as a developer, I should just fix the interaction of my component with the child.

I can of course write a smart mock that wraps the original component without changing its behaviour. But that means considering implementation details in test environment.

It also creates problems when a third-party component is used in several projects: you will have to put mocks in a separate package or copy them in each project. In any case, you will have to monitor the relevance of this mock.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

addamove picture addamove  路  3Comments

jalvarado91 picture jalvarado91  路  3Comments

alejandronanez picture alejandronanez  路  4Comments

joshvillahermosa picture joshvillahermosa  路  3Comments

mezei picture mezei  路  3Comments