59

In enzyme you can check for the existence of child component like this:

expect(wrapper.find(ChildComponent)).toHaveLength(1)

What is the equivalent to this test in react testing library? All the online examples I find just cover very simple tests looking for dom elements - none include examples of that render child components. How do you find a child component?

Flip
  • 6,233
  • 7
  • 46
  • 75
riscos3
  • 1,617
  • 2
  • 11
  • 16

5 Answers5

32

You shouldn't check if your child component is rendered or not, because it's testing implementation details (which testing library doesn't encourage you to do).

You can check some text from your child component is rendered or you can give data-testid to your wrapper element in child and then use .toBeInTheDocument from @testing-library/jest-dom

expect(getByText(/some text/i)).toBeInTheDocument();

or

expect(getByTestId('your-test-id')).toBeInTheDocument();

Updated: Example

// Child Component
function ChildComponent() {
    return <div>Child Element</div>;
};

// Parent
export default function Parent() {
    return (
        <div className="App">
            <ChildComponent />
        </div>
    );
}

Test:

import { render } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import Parent from "./App";

test("test child component", () => {
  const { getByText } = render(<Parent />);
  expect(getByText(/child element/i)).toBeInTheDocument();
});
Amit Chauhan
  • 6,151
  • 2
  • 24
  • 37
  • 19
    `You shouldn't check if your child component is rendered or not, because it's testing implementation details` - that's simply not true. For example I want to check if a parent component shows a spinner while fetching data. That's not implementation details, unless I look for a particular spinner class etc. – Mikhail Batcer Aug 01 '21 at 13:42
  • 1
    @MikhailBatcer if you test text inside your child component is there or not is not testing implementation details. – Amit Chauhan Aug 06 '21 at 13:54
  • 3
    @AmitChauhan Some components don't have text. Like spinner for example. – Mikhail Batcer Aug 16 '21 at 18:50
  • @MikhailBatcer such elements can have test id and you can check if that test id is rendered or not. – Amit Chauhan Aug 17 '21 at 04:59
  • @AmitChauhan So the test will rely on such an implementation detail as whether or not a particular HTML element with the test ID is present in the component. – Mikhail Batcer Aug 18 '21 at 12:48
  • I don't think testID is implementation details. Testing implementation detail is if you test wether particular function was called or not i.e in class base component if you test member function was called or not. So if you change implementation from class to functional component your test will fail. But if you test wether testID is present or not even if you switch from class to functional implementation your test will still pass. – Amit Chauhan Aug 18 '21 at 13:29
  • 3
    But if I test by the text that the component is displaying, every time I change the component text I need to update the component tests and all other components that use that component – Raz Luvaton Aug 18 '21 at 16:53
  • 1
    @RazLuvaton FYI: according to [Kent C. Dodds](https://kentcdodds.com/blog/testing-implementation-details), implementation details are things that users of your code will not typically use, see, or even know about. But if the text is something being rendered as output that production users can see, then definitely we can use it for testing. – Topman Dec 21 '22 at 11:26
  • If you're testing the existence of arbitrary text, how is that any less of an implementation detail than checking which component is responsible for that text? You, as the Parent, should have no concern as to what the Child is doing, regardless of whether or not it appears in the DOM. If you have to peak inside the contents of the Child, _then you are by definition testing its implementation!_ – user3781737 Feb 17 '23 at 20:10
10

You can use @testing-library/jest-dom library.

Component:

<div role="root">       
    <div data-testid="parent">
        <div data-testid="child">
            content
        </div>
    </div>
</div>

Test:

import '@testing-library/jest-dom'
import {render} from '@testing-library/react';

describe('My component', () => {
    test('should render component2', () => {
        const { getByRole, getByTestId } = render(<Component/>);

        const root = getByRole('root');
        const parent = getByTestId('parent');
        const child = getByTestId('child');

        expect(root).toContainElement(parent);
        expect(parent).toContainElement(child);
        
        expect(child).not.toContainElement(parent); // Pass
        expect(child).toContainElement(parent); // Throw error!    
    });
});

Another solution is to use within function from @testing-library/react library:

import { within } from '@testing-library/react';
...

expect(within(parent).queryByTestId('child')).not.toBeNull();
Nolesh
  • 6,848
  • 12
  • 75
  • 112
6

I used React Test Renderer for that purpose:

import TestRenderer from 'react-test-renderer';

import ItemList from 'components/ItemList';
import LoadingIndicator from 'components/LoadingIndicator';

test('renders loading indication', () => {
    const testRenderer = TestRenderer.create(
        <ItemList items={[]} isLoading={true}/>
    );
    const testInstance = testRenderer.root;
    testInstance.findByType(LoadingIndicator);
});

I don't think that it's "testing implementation details", quite the opposite - LoadingIndicator component can be modified without need to fix the test case.

Mikhail Batcer
  • 1,938
  • 7
  • 37
  • 57
  • Thanks @Mikhail Batcer One better practice for controls the expect behavior for this is `expect(() => root.findByType(WichedComponent)).not.toThrow()` – Felipe Gustavo Oct 27 '21 at 20:23
5

I agree with everyone who said, that checking for either text or a test-id inside a child component is testing implementation details.

But we could use mocking, to get rid of the implementation details of the child component.

The code to test:

import { ChildComponent } from 'child-from-somewhere';

export function Parent() {
    return (
        <div className="App">
            <ChildComponent />
        </div>
    );
}

The test code that checks, if ChildComponent was rendered:

import { render } from "@testing-library/react";
import React from "react";
import { Parent } from "./parent";

jest.mock("child-from-somewhere", () => ({
    ChildComponent: () => <div data-testid="ChildComponent">ChildComponent</div>,
}));

describe("ChildComponent component", () => {
    it("should be in the document", () => {
        const { getByTestId } = render(<Parent />);
        expect(getByTestId("ChildComponent")).toBeInTheDocument();
    });
});

That way, the test stays independent of changes that are made inside of ChildComponent.

m.reuter
  • 51
  • 1
  • 3
4

since query* return null if element is not found you may just

expect(queryByTestId('test-id-you-provided')).toEqual(null);
expect(queryByTestId('some-other-test-id-you-provided')).not.toEqual(null);

Also getBy* throws if element is not find. So next one should work as well

getByTestId('test-id-you-provided'); 
skyboyer
  • 22,209
  • 7
  • 57
  • 64