Search code examples
reactjsjestjsreact-routerreact-testing-library

Why does my render function render an empty <body> when I add a <Router> to my test in Jest?


I am trying to test the below scenario with React Router V3. Based on the step prop, I set some state variables and then render the component. The correct screen is then displayed based on the props passed to

const TestComponent = ({
  languageData,
  locale,
  route: { step },
}) => {
  const [showTiles, setShowTiles] = useState(true);
  const [showAdsSearch, setShowAdsSearch] = useState(false);
  const [showBasicSearch, setShowBasicSearch] = useState(false);
  const [showSubmitterFields, setShowSubmitterFields] = useState(false);

  const viewHome = () => {
    setShowBasicSearch(false);
    setShowSubmitterFields(false);
    setShowAdsSearch(false);
    setShowTiles(true);
  };

  const viewBasicSearch = () => {
    setShowBasicSearch(true);
    setShowSubmitterFields(false);
    setShowAdsSearch(false);
    setShowTiles(false);
  };

  const viewAdSearch = () => {
    setShowBasicSearch(false);
    setShowSubmitterFields(false);
    setShowAdsSearch(true);
    setShowTiles(false);
  };

  const viewCreateSubmitterProfileFields = () => {
    setShowBasicSearch(false);
    setShowSubmitterFields(true);
    setShowAdsSearch(false);
    setShowTiles(false);
  };

  const stepMap = new Map([
    [1, viewHome],
    [2, viewCreateSubmitterProfileFields],
    [3, viewBasicSearch],
    [4, viewAdSearch],
  ]);

  useEffect(() => {
    const currentState = stepMap.get(step) || viewHome;
    currentState();
  }, [step]);

  if (Object.entries(languageData).length === 0) {
    return <ProgressCircle />;
  }

  return (
    <IntlProvider locale={locale} messages={languageData}>
      <UserContext.Consumer>
        {(user) => (Object.keys(user).length > 0 ? (
          <HomeView
            className={styles.grayBg}
            showAdsSearch={showAdsSearch}
            setShowAdsSearch={setShowAdsSearch}
            showSubmitterFields={showSubmitterFields}
            setShowSubmitterFields={setShowSubmitterFields}
            showBasicSearch={showBasicSearch}
            setShowBasicSearch={setShowBasicSearch}
            showTiles={showTiles}
            setShowTiles={setShowTiles}
          />
        ) : <div>NO USER CONTEXT SET IN ROOT MODULE</div>)}
      </UserContext.Consumer>
    </IntlProvider>
  );
};

The home component:

export function Home({
  showTiles,
  setShowTiles,
  showBasicSearch,
  setShowBasicSearch,
  showAdsSearch,
  setShowAdsSearch,
  showSubmitterFields,
  setShowSubmitterFields,
}) {

  return (
    <React.Fragment>
      <HeaderSection
        setShowBasicSearch={setShowBasicSearch}
        showBasicSearch={showBasicSearch}
        setShowTiles={setShowTiles}
        setShowAdsSearch={setShowAdsSearch}
        setSubmitterIdToView={setSubmitterIdToView}
        setSubmitterName={setSubmitterName}
        setShowSubmitterFields={setShowSubmitterFields}
      />
      <div id="home" className={styles.grayBgForHome}>
        {showTiles && (
          <TilesView
            setShowBasicSearch={setShowBasicSearch}
            setShowTiles={setShowTiles}
            setShowAdsSearch={setShowAdsSearch}
            showAdsSearch={showAdsSearch}
            showSubmitterFields={showSubmitterFields}
            setShowSubmitterFields={setShowSubmitterFields}
          />
        )}
        {showBasicSearch && (
          <React.Fragment>
            <h1 className={styles.headingText}>
              Search Submitter Profile
            </h1>
            <ConnectedBasicSearch/>
          </React.Fragment>
        )}
        {showAdsSearch && (
          <React.Fragment>
            <h1 className={styles.headingText}>
              Advanced Search Submitter Profile
            </h1>
            <ConnectedAdvancedSearch/>
          </React.Fragment>
        )}
        {showSubmitterFields && (
          <React.Fragment>
            <h1 className={styles.headingText}>Create Submitter Profile</h1>
            <ConnectedSubmitterProfile/>
          </React.Fragment>
        )}
      </div>
      <FooterSection />
    </React.Fragment>
  );
}

In my test, I have the below. What I am trying to do is render Home with step = 1 which should display the Tiles component. Then I click on the #basicSearchTile which is a Link to /submitter-profile/basic-search and want to check for certain elements being displayed:

async function clickElement(container, selector) {
  const user = userEvent.setup();
  const element = container.querySelector(selector);
  await user.click(element);
}

async function checkDisplayed(container, selector, shouldBeDisplayed) {
  const element = container.querySelector(selector);
  if (shouldBeDisplayed) {
    expect(element).toBeInTheDocument();
  } else {
    expect(element).not.toBeInTheDocument();
  }
}

const props1 = {
  languageData: { intlKeyMock: 'intlValueMock' },
  locale: 'localeMock',
  route: { step: 1 },
};
const props2 = {
  languageData: { intlKeyMock: 'intlValueMock' },
  locale: 'localeMock',
  route: { step: 2 },
};
const props3 = {
  languageData: { intlKeyMock: 'intlValueMock' },
  locale: 'localeMock',
  route: { step: 3 },
};
const props4 = {
  languageData: { intlKeyMock: 'intlValueMock' },
  locale: 'localeMock',
  route: { step: 4 },
};

  describe('Navigation tests', () => {
    it('invalid submitterId on save for edit profile', async () => {
      const { container } = render(
        <Router initialEntries={['/submitter-profile']} history={createMemoryHistory()}>
          <UserContext.Provider value={loggedInUser}>
            <Route path="/" element={<TestableAxpSubmissionsWebSearchSubmitterProfile {...props1} />} />
            <Route path="/submitter-profile" element={<TestableAxpSubmissionsWebSearchSubmitterProfile {...props1} />} />
            <Route path="/submitter-profile/basic-search" element={<TestableAxpSubmissionsWebSearchSubmitterProfile {...props3} />} />
            <Route path="/submitter-profile/create" element={<TestableAxpSubmissionsWebSearchSubmitterProfile {...props2} />} />
            <Route path="/submitter-profile/advanced-search" element={<TestableAxpSubmissionsWebSearchSubmitterProfile {...props4} />} />
          </UserContext.Provider>
        </Router>
      );
      screen.debug();
      await clickElement(container, '#basicSearchTile');
      await clickElement(container, '#search-button');
      await clickElement(container, '#resultTableRowsubmitterId123');
      await clickElement(container, '#editToggle');
      await clickElement(container, '#saveButton');
      screen.debug();
      await checkDisplayed(container, '#responseModalMsg', true);
      await checkTextValue(container, '#responseModal', 'Must enter a valid value for Submitter ID.');
      await clickElement(container, '#responseModal');
      await checkDisplayed(container, '#responseModalMsg', false);
    });
});

However, whenever i use render, the screen.debug() calls are returning an empty body .

<body>
  <div />
</body>

Also I have added the route definitions in case they are helpful. Routing in the app is working great. it is just the tests that I cannot get to work

const Routes = [
  <Route
    key="submitter-profile-route"
    path="/submitter-profile"
    moduleName="axp-submissions-web-search-submitter-profile"
    step={1}
  />,
  <Route
    key="create-submitter-profile-route"
    path="/submitter-profile/create"
    moduleName="axp-submissions-web-search-submitter-profile"
    step={2}
  />,
  <Route
    key="basic-search-submitter-profile-route"
    path="/submitter-profile/basic-search"
    moduleName="axp-submissions-web-search-submitter-profile"
    step={3}
  />,
  <Route
    key="advanced-search-submitter-profile-route"
    path="/submitter-profile/advanced-search"
    moduleName="axp-submissions-web-search-submitter-profile"
    step={4}
  />];

Solution: I was able to us Drews answer to mock my router like this

const TestHomeComponent = () => <UserContext.Provider value={loggedInUser}><TestableAxpSubmissionsWebSearchSubmitterProfile {...props1} /></UserContext.Provider>;
const TestBasicSearchComponent = () => <UserContext.Provider value={loggedInUser}><TestableAxpSubmissionsWebSearchSubmitterProfile {...props3} /></UserContext.Provider>;
const TestCreateComponent = () => <UserContext.Provider value={loggedInUser}><TestableAxpSubmissionsWebSearchSubmitterProfile {...props2} /></UserContext.Provider>;
const TestAdvSearchComponent = () => <UserContext.Provider value={loggedInUser}><TestableAxpSubmissionsWebSearchSubmitterProfile {...props4} /></UserContext.Provider>;

const { container, debug } = render(
  <Router initialEntries={['/submitter-profile']} history={createMemoryHistory()}>
    <UserContext.Provider value={loggedInUser}>
      <Route path="/submitter-profile" component={TestHomeComponent} />
      <Route path="/submitter-profile/basic-search" component={TestBasicSearchComponent} />
      <Route path="/submitter-profile/create" component={TestCreateComponent} />
      <Route path="/submitter-profile/advanced-search" component={TestAdvSearchComponent} />
      <Route path="/" component={TestHomeComponent} />
    </UserContext.Provider>
  </Router>
);

Solution

  • The React-Router v3 Route component doesn't have an element prop, it has a component prop. Update the test code to use the component prop and pass a reference to the component instead of JSX.

    Be sure to also specify the routes in the inverse order of path specificity, e.g. render more specific paths before less specific paths.

    Example:

    describe('Navigation tests', () => {
      it('invalid submitterId on save for edit profile', async () => {
        const memoryHistory = createMemoryHistory();
    
        const { container } = render(
          <Router
            initialEntries={['/submitter-profile']}
            history={memoryHistory}
          >
            <UserContext.Provider value={loggedInUser}>
              <Route
                path="/submitter-profile/basic-search"
                component={TestableAxpSubmissionsWebSearchSubmitterProfile}
              />
              <Route
                path="/submitter-profile/create"
                component={TestableAxpSubmissionsWebSearchSubmitterProfile}
              />
              <Route
                path="/submitter-profile/advanced-search"
                component={TestableAxpSubmissionsWebSearchSubmitterProfile}
              />
              <Route
                path="/submitter-profile"
                component={TestableAxpSubmissionsWebSearchSubmitterProfile}
              />
              <Route
                path="/"
                component={TestableAxpSubmissionsWebSearchSubmitterProfile}
              />
            </UserContext.Provider>
          </Router>
        );
    
        ...
      });
    });