Test which uses MemoryRouter to render a component requiring parameters is not working, 'unable to find role: heading'

Viewed 35

I am trying to unit test the Profile component which contains a userParams() call to get the userID from the path variable, to display the information for that user in a profile format.

The test uses MemoryRouter to route to the test with the correct parameter, and I am unsure why it is returning that it is unable to find 'role' 'heading' when there are 2 heading elements in the JSX component which should by default be given the role 'heading'.

I am unsure if the test is configured correctly, or if there is some other cause.

Fails with:

Unable to find role="heading"

Ignored nodes: comments, <script />, <style />
<body>
  <div />
</body>

Ignored nodes: comments, <script />, <style />
<html>
  <head />
  <body>
    <div />
  </body>
</html>
Error: Unable to find role="heading"

Ignored nodes: comments, <script />, <style />
<body>
  <div />
</body>

Profile.test.js

import React from 'react'

// import API mocking utilities from Mock Service Worker
import {rest} from 'msw'
import {setupServer} from 'msw/node'

// import react-testing methods
import {render, fireEvent, waitFor, screen} from '@testing-library/react'
import routeData, {MemoryRouter, Route, Routes} from 'react-router';

// add custom jest matchers from jest-dom
import '@testing-library/jest-dom';

import config from "../configuration/Configuration.json";
import {Profile} from "../pages/Profile";

const userId = 1;

const server = setupServer(
  rest.get(
    config.URL + '/users/' + userId,
    (req, res, ctx) => {
      return res(ctx.json({
        userId: 1,
        email: 'testEmail@outlook.com',
        password: null,
        firstName: 'Test',
        lastName: 'User'
      }))
    }
  ),
)

const renderWithRouter = ({ children }) => (
  render(
    <MemoryRouter initialEntries={['/users/1']}>
      <Routes>
        <Route path='/users/:userId'>
          {children}
        </Route>
      </Routes>
    </MemoryRouter>
  )
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

test('loads and displays profile', async () => {
  renderWithRouter(<Profile />)

  await waitFor(
    () => screen.getByRole("heading").toHaveTextContent("Test User")
  )
})

Profile.js:

import React from 'react'
import {useEffect, useState} from "react";
import UserService from "../services/UserService";
import {getFullName} from "../util/Util";
import '../css/profile.css';
import {useParams} from "react-router-dom";

export const Profile = () => { 
  //array of compatible users fetched for a user.
  const [userProfileInformation, setUserProfileInformation] = useState([]);
  const [isLoading, setLoading] = useState(true);

  const { userId } = useParams();

  useEffect(() => {
    getUserProfileInformation().then(() => {
      setLoading(false);
    });
  }, []);

  const getUserProfileInformation = async () => {
    const response = await UserService.getUserProfileInformation(userId)
      .then(response => response.json())
      .then(data => {
        setUserProfileInformation(data);
      });
  }

  if (isLoading) {
    return (
      <div id="loading">
        <h2>Loading...</h2>
      </div>
    )
  }

  return (
    <div>
      <div className="profileCard">
        <h1 name='fullName'>
          {getFullName(
            userProfileInformation.firstName, 
            userProfileInformation.lastName
          )}
        </h1>
        <h2>{userProfileInformation.email}</h2>
      </div>
    </div>
  )
}
1 Answers

Issue

I think the issue lies with your test render function renderWithRouter.

  1. renderWithRouter is rendering the children "prop" as a child of the Route, but the only valid children of Route is another Route in the case of nesting routes. The children prop should be passed to the Route component's element prop.
  2. renderWithRouter is a regular function, not a React component, so its argument isn't a props object. The test is passing <Profile /> as an argument and renderWithRouter is attempting to destructure a children property from it. `children is OFC undefined.

Solution

Update renderWithRouter to consume an element argument that is passed to the Route component's element prop.

const renderWithRouter = (element) => (
  render(
    <MemoryRouter initialEntries={['/users/1']}>
      <Routes>
        <Route path='/users/:userId' element={element} />
      </Routes>
    </MemoryRouter>
  )
);
Related