0

This is the react class that I want to test. I am using Content.Provider in order to keep a global state. In my testing file, I name it <Store>.

import { useEffect, useContext } from "react";
import { Context } from '../../config/Store';

const ProjectPage = () => {
    const [globalState, dispatch] = useContext(Context);

    useEffect(() => {
        getAllProjectData();
    }, []);

    const getAllProjectData = async () => {
        console.log('### getAllProjectData - ProjectPage.jsx')
        try {
            const data = await getProjectData();
            dispatch({ type: 'setIsLoaded', payload: true })
            dispatch({ type: 'setError', payload: false })
            dispatch({ type: 'setProjectDetails', payload: data })
        } catch (error) {
            console.log("### Error: ", error)
            dispatch({ type: 'setProjectDetails', payload: null })
            dispatch({ type: 'setIsLoaded', payload: true })
            dispatch({ type: 'setError', payload: true })
        }
    }

    if (globalState.error) {
        return <div><ErrorPage /></div>;
    } else if (!globalState.isLoaded) {
        return <div className="spinnerContainer" data-testid="spinnerContainer" 
                  <CircularProgress className="spinner" color="error" size="3rem" thickness={7} />
               </div>;
    } else {
        return <div id="projectToolbar" data-testid="projectTabBar" className="container-fluid"></div>;
    }
}

export default ProjectPage;

Then I have this test.js file in which the first test succeeds and the second fails.

import Store from '../../config/Store';
import ProjectPage from './ProjectPage';
import { render } from '@testing-library/react';

test('renders the spinner before the project page', () => {
    const { getByTestId } = render(<Store><ProjectPage /></Store>);
    const spinnerContainer = getByTestId("spinnerContainer");
    expect(spinnerContainer).toBeInTheDocument();
});

test.only('renders the project tab bar', async () => {
    render(<Store><ProjectPage /></Store>);
    const projectTabBar = await waitFor(async () => await screen.findByTestId("projectTabBar"))
    expect(projectTabBar).toBeInTheDocument();
});

In my logger the test shows the following:

test result

As you can see the test fails because data-testid="projectTabBar" is not in the document and the spinner is there. I understand that this is happening because the component doesn't render the data but I don't understand why is this happening and how to resolve it. Any help would be appreciated.

Fotios Tragopoulos
  • 449
  • 1
  • 7
  • 24

1 Answers1

0

Your first test and second test look identical in their setup, so it is expected that they will return the same result. The reason you are seeing the spinner is that at the point you are performing your assertion, your data is still loading. There are a few approaches you can take.

  1. Test the real implementation of the Store, which involves waiting for your getAllProjectData function to complete and the component to rerender. This can lead to flakey and long running tests, but will test the component end-to-end. Look at the testing library async functions documentation on how to do this and pay particular attention to the timeouts.

  2. Abstract the getAllProjectData function and useEffect into a custom hook and use that in the component instead. This allows you to mock the implementation of the custom hook in your tests and synchronously set the loading, error, or data states of your Store. Note that you will still have to wait for the projectTabBar to appear because on the initial render the Store won't have been initialised, but the wait should be short and consistent, which results in quicker and less flakey tests, but you will also need to write tests for the custom hook separately.

Stuart Nichols
  • 1,026
  • 1
  • 8
  • 11
  • Great explanation and hint on where to look. I changed my test using async/await according to the documentation provided and updated my test in the question. What happens is that the `getAllProjectData` is being called in reality but not in the test. – Fotios Tragopoulos Jan 03 '22 at 14:00
  • Try changing ```const projectTabBar = await waitFor(async () => await screen.findByTestId("projectTabBar"))``` to ```const projectTabBar = await screen.findByTestId("projectTabBar", { timeout: 10000})```. The waitFor is not needed and the default timeout is only 1 second, which may not be long enough. – Stuart Nichols Jan 03 '22 at 14:25