props to @Estus Flask for a super helpful answer. I was using just the setTimeout functionality before, but couldn't get tests to work at all. Calling setTimeout
in the tests was causing nested calls, jest.useFakeTimers()
and jest.runAllTimers()
didn't seem to do anything, and I was stuck getting the loader back. Since searching for the right way to test this took so long, I figured it would be helpful share how I was able to test it. With an implementation per the given solution:
import React, { ReactElement, Suspense } from 'react';
import { Outlet, Route, Routes } from 'react-router-dom';
import Loader from 'app/common/components/Loader';
const Navigation = React.lazy(() => {
return Promise.all([
import("./Navigation"),
new Promise(resolve => setTimeout(resolve, 300))
])
.then(([moduleExports]) => moduleExports);
});
const Home = React.lazy(() => {
return Promise.all([
import("./Home"),
new Promise(resolve => setTimeout(resolve, 300))
])
.then(([moduleExports]) => moduleExports);
});
interface PagesProps {
toggleTheme: () => void;
}
const Pages = (props: PagesProps): ReactElement => (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/" element={
<>
<Navigation toggleTheme={props.toggleTheme}/>
<Outlet />
</>
}>
<Route index element={<Home />} />
</Route>
</Routes>
</Suspense>
);
export default Pages;
I was able to successfully test it with the following. Note that if you don't include jest.useFakeTimers()
and jest.runAllTimers()
you'll see flaky tests. There's a little excess detail in my tests because I'm also testing pushing the history (in other tests), but hopefully this helps anyone else!
/**
* @jest-environment jsdom
*/
import { render, screen, cleanup, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import Pages from './';
describe('Pages component', () => {
beforeEach(() => {
jest.useFakeTimers();
})
const history = createMemoryHistory();
it('displays loader when lazy', async () => {
render(
<Router location={history.location} navigator={history} navigationType={history.action}>
<Pages toggleTheme={function (): void { return null; } } />
</Router>,
);
const lazyElement = await screen.findByText(/please wait/i);
expect(lazyElement).toBeInTheDocument();
});
it('displays "Welcome!" on Home page lazily', async () => {
render(
<Router location={history.location} navigator={history} navigationType={history.action}>
<Pages toggleTheme={function (): void { return null; } } />
</Router>,
);
const fallbackLoader = await screen.findByText(/please wait/i);
expect(fallbackLoader).toBeInTheDocument();
jest.runAllTimers();
const lazyElement = await screen.findByText('Welcome!');
expect(lazyElement).toBeInTheDocument();
});
afterEach(cleanup);
});