I am trying to write unit tests for a react application that calls multiple static API endpoints to populate a bunch of different dropdown element options.
A stripped down version of my react component is below. I have made a custom hook
useFetchReferenceData
that takes care of calling all the reference APIs and setting the dropdown options.
export function SearchSubmitterProfile() {
const [search, setSearch] = useState(true);
const [viewResults, setViewResults] = useState(false);
const [createSubmitterProfileState, setCreateSubmitterProfileState] = useState(INITIAL_STATE);
useFetchReferenceData(
createSubmitterProfileState,
setCreateSubmitterProfileState
);
return (
<div id="search-sub-profile" key="search-sub-profile" className={styles.grayBg}>
{search ? (
<ConnectedSearchView
/>
) : null}
{viewResults ? (
<ConnectedSubmitterProfile
/>
) : null}
</div>
);
}
export default SearchSubmitterProfile;
In useFetchReferenceData, I have about 14 of the below fetch calls and dropdown builder functions like the below. Each of these functions is called in an asynchronous useEffect at the bottom of the custom hook file
const { run: getLanguageIndicators } = useFetchye(`${subApiUrl}/submlanguageind`, {
credentials: 'include',
headers: getDefaultHeaders(),
defer: true,
});
const buildLanguagIndDropdown = async () => {
const currentState = { ...createSubmitterProfileState };
const { data } = await getLanguageIndicators();
if (data.status === 200) {
const result = [];
result.push(DROPDOWN_STARTER);
data.body.map((element) => result.push({
value: `${element.dialect_language_code}`,
label: `${element.dialect_language_code} - ${element.dialect_name}`,
}));
currentState.staticFieldState.submitterLanguageIndicator.options = result;
setCreateSubmitterProfileState(currentState);
} else {
errorList.push('Error retrieving language indicators');
}
};
useEffect(() => {
const fetchAllData = async () => {
await buildRegionsDropDown();
await buildLanguagIndDropdown();
... 14 more of these
};
fetchAllData().catch(console.error);
}, []);
In my tests I am trying to mock all of these fetch calls to cover all of the lines in the useFetchReferenceData file. The test looks like the below, and I am using the jest-fetch-mock library to mock the series of API calls.
describe('Search Submitter Profile reference data', () => {
beforeEach(() => {
enableFetchMocks();
fetch.mockResponses(
[
JSON.stringify(mockRegions),
{ status: 200 },
],
[
JSON.stringify(mockLanguageIndicators),
{ status: 200 },
],
... 14 more
);
});
it('validate the search information view', async () => {
render(
<UserContext.Provider value={{ loggedInUser }}>
<SearchSubmitterProfile />
</UserContext.Provider>
);
});
});
The above test passes, but I get a bunch of the following warnings that I am wondering how to fix.
console.error
Warning: An update to SearchSubmitterProfile inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
Your test doesn't appear to test anything - it calls render
but does nothing after that. Did you omit your expect
calls from your code snippet?
Anyway, the warnings are telling you that the fetch
calls are finishing asynchronously, which triggers re-renders, and the test code doesn't approve of React re-rendering without knowing about. If you were triggering re-renders synchronously, by simulating user input, then you could wrap those user input events in act
(see this example). Since they're occurring asynchronously, that won't work. You have a couple of alternatives:
render
call in act
. I have not tried this approach.waitFor
or one of the findBy
queries to make sure that any background operations have finished before your test continues. This is my normal approach. it('validate the search information view', async () => {
render(
<UserContext.Provider value={{ loggedInUser }}>
<SearchSubmitterProfile />
</UserContext.Provider>
);
await screen.findByText('something that only displays once all fetches finish');
// Or
await waitFor(() => expect(someComplexCriteria).toBeTruthy());
// Continue with test assertions
});