Search code examples
javascriptreactjsreact-testing-librarytesting-library

React testing library Router tests not passing when following RTL's own guide


I am attempting to do a simple test on a simple navigation app using React Testing Library. I am currently attempting to use the example method shown in RTL's own guide, however that doesn't seem to be working.

App.js

return (
    <BrowserRouter>
      <div>
        <Route exact path="/" component={Home} />
        <Route exact path="/hello" component={Hello} />
      </div>
    </BrowserRouter>
  );

Home.jsx

return (
    <div data-testid="home-container">
      <Link to="/hello">Link to hello</Link>
    </div>
  );

Hello.jsx

return (
    <div data-testid="hello-container">
      <Link to="/">&lt; Back</Link>
      <h1 data-testid="hello-title">Hello component</h1>
    </div>
  );

Test for Home.jsx in Home.spec.js

test("Navigate to hello page when clicked", async () => {
    const history = createMemoryHistory();
    render(
      <Router history={history}>
        <Home />
      </Router>
    );

    // check pre-nav page
    expect(screen.getByText(/Link to hello/i)).toBeInTheDocument();

    const leftClick = { button: 0 };
    userEvent.click(screen.getByText(/Link to hello/i), leftClick);

    // check we have navigated to the correct path - THIS PASSES!
    expect(history.location.pathname).toBe("/hello");

    // check that the content changed to the new page - THIS FAILS!
    expect(screen.getByText(/Hello component/i)).toBeInTheDocument();
  });

Error...

The error I seem to be getting is that it can't find the /Hello component/ text, still thinking that it's on the Home component... Which is weird because the history location check clearly shows it being on the Hello path.

Unable to find an element with the text: /Hello component/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, <script />, <style />
<body>
  <div>
    <div
      data-testid="home-container"
    >
      <a
        href="/hello"
      >
        Link to hello
      </a>
    </div>
  </div>
</body>

Solution

  • Thanks to @BrunoNoriller for helping me to see the errors of my ways here.

    The main issue I was having with my original question is that was confusing myself with what I thought I being tested.

    I thought that I was performing a unit test on my component, simply testing the routing that goes on from the component to show the <Hello/> component. In fact, what I was really doing was a pseudo-E2E test of the wider app and its routing, in which - yes - Bruno's answer was correct, to render the <App /> component and use the window history to let the test know where we are/where we go.

    What I really wanted was to unit test the component in isolation, and I'd simply forgotten the scope of doing this. The main analogy would be to think of your component taking in some sort of handleClick function to trigger when you click a button in that component. When it comes to testing, you don't really care what that triggerable function is, just that we pass one in and test that it fires. This is similar to isolation testing routing within a component... Say you have a <Link> that when clicked will route like my component to /hello; We don't actually care about the component or page that is rendered at the end of this (like you would with an E2E test), we just care that something is rendered, and we can control this in our test.

    Home.jsx

    return (
        <div data-testid="home-continer">
            <Link to="/hello">Link to hello</Link>
        </div>
    );
    

    Hello.jsx

    return (
        <div data-testid="hello-continer">
            <Link to="/">Back</Link>
            <div>This is the real hello component</div>
        </div>
    );
    

    Home.spec.js

    test('should render whatever is at /hello path when clicked', () => {
        /* 
          Essentially we are rendering a new, custom router for the 
          test to route to a test-specific version of the route we 
          render in the component under test
        */
        render(
          <BrowserRouter>
            <Home />
            <Route path="/hello">
              <div data-testid="hello-container">
                 TEST HELLO
              </div>
            </Route>
          </BrowserRouter>
        );
    
        // Test we're at the correct starting point in the <Home> component
        expect(rendered.getByText(/Link to hello/i)).toBeInTheDocument();
    
        // Click the link to the /hello path
        fireEvent.click(rendered.getByText(/Link to hello/i));
    
        // Test we can now see the path-specific component contents
        expect(rendered.getByTestId("hello-container")).toBeTruthy();
    
        /* 
          This test shows that we are showing the test-specific path 
          contents, not the real <App/> path route component contents
        */
        expect(rendered.getByTestId("hello-container").textContent)
            .toContain("TEST HELLO");
    
    });