How do I test that a child component is rendered?
Asked Answered
D

5

63

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?

Doorframe answered 3/2, 2020 at 14:41 Comment(1)
Maybe expect(wrapper).toContain('ChildComponent')?Home
V
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();
});
Virtuous answered 4/2, 2020 at 7:0 Comment(9)
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.Skein
@MikhailBatcer if you test text inside your child component is there or not is not testing implementation details.Virtuous
@AmitChauhan Some components don't have text. Like spinner for example.Skein
@MikhailBatcer such elements can have test id and you can check if that test id is rendered or not.Virtuous
@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.Skein
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.Virtuous
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 componentReedreedbird
@RazLuvaton FYI: according to Kent C. Dodds, 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.Pradeep
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!Malaise
S
10

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.

Skein answered 16/8, 2021 at 18:59 Comment(1)
Thanks @Mikhail Batcer One better practice for controls the expect behavior for this is expect(() => root.findByType(WichedComponent)).not.toThrow()Cyd
Z
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();
Zucker answered 30/11, 2021 at 10:56 Comment(0)
B
9

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.

Behoof answered 19/12, 2022 at 14:18 Comment(0)
S
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'); 
Scapular answered 3/2, 2020 at 15:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.