I'm trying to write a test (with react-testing-library) for a component that makes an api call inside a useEffect and then updates redux state. The component should then update with the new state, but in my test, the component never seems to get updates to redux state.
I've tried logging at each step of the process: I found that the redux state was updating as expected during the batched actions in apiCall(). Then when "otherReduxAction" is called, it seems to be acting on a stale store. Moving that action out of the component fixed the problem. Same with the subsequent "setIsFetching" call. However, while moving those actions out of the component helped to persist state changes, the component still never gets the state updates.
This behavior only exists in testing. The app updates as expected.
component:
const CallsList = () => {
const dispatch = useDispatch();
const { filteredCalls, isFetching } = useSelector(selectState);
useEffect(() => {
async function fetchRecordings() {
dispatch(setIsFetching(true));
try {
await apiCall();
dispatch(otherReduxAction());
} catch (err) {}
dispatch(setIsFetching(false));
}
fetchRecordings();
}, [dispatch]);
return (
isFetching ? (
RENDER PROGRESS BAR
) : filteredCalls.length ? (
RENDER LIST OF CALLS
) : (
RENDER NO RESULTS MESSAGE
)
);
};
apiCall:
export const apiCall = (): Promise<string> => {
return new Promise(async (res, rej) => {
try {
const response = await axios.get<GetResponse>(
`${process.env.REACT_APP_API_BASE_URL}/recordings`,
);
batch(() => {
store.dispatch(setCalls(response.data.recordings));
... DISPATCH ADDITIONAL ACTIONS
});
res("success");
} catch (err) {
rej("error");
}
});
};
msw handler:
rest.get(`${process.env.REACT_APP_API_BASE_URL}/recordings`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ MOCKED RESPONSE OBJECT })
);
}),
custom react-testing-library render:
import { render as rtlRender } from "@testing-library/react";
...
export const render = (ui: any, initialStore = {}, { route = '/' } = {}, options = {}, ) => {
const store = createStore(rootReducer, initialStore);
const Providers = ({ children }: any) => (
<Provider store={store}>
<BrowserRouter>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</BrowserRouter>
</Provider>
);
window.history.pushState({}, 'Test page', route)
return rtlRender(ui, { wrapper: Providers, ...options });
};
test (this fails on awaiting the element with LIST ITEM TEXT:
describe("CallsList", () => {
it("renders list of recordings", async () => {
render(<CallsList />);
const progressBar = screen.getByRole("progressbar");
expect(progressBar).toBeInTheDocument();
await waitForElementToBeRemoved(circularProgress)
expect(await screen.findByText(LIST ITEM TEXT)).toBeInTheDocument();
});
});