Search code examples
reactjstestingreact-testing-librarydynamic-importvitest

how to debug a test timing out waiting for React.lazy import


I have a React+vite app for which I'm writing test to cover the front-end routing redirection logic at application startup.

Routing is handled by react-router v6, and all components associated with routes are wrapped in React.lazy. Tests are ran by vitest and I'm using react-testing-library helpers

All the tests are similar and look like this

it('Redirects from app root to red room if the user has a red shirt', async () => {
    getUser.mockReturnValue(redShirtUser);
    render(MyTestedComponent, { wrapper });

    await waitFor(() => expect(screen.getByText('Welcome to the red room'));
    expect(history.location.pathname).toBe('/red-room');
  });

One of the tests, though, is taking significantly longer than the others, to the point that waitFor times out. I can specify a longer timeout to waitFor, but it will still not reliably run on the CI. This happens also if the test is the only one in its file/the only one being executed.

I have narrowed down the slow part (through the magic of console.log debugging) to be the lazy import() statement - it takes a lot (seconds) until the module is imported and executed.

How can I debug this? Are there things known to cause (lazy) imports to become slow?


Solution

  • Node's module resolution is very slow. So if your script imports/requires a lot of other scripts/dependencies then your startup time is going to be very slow.

    If you're doing lazy imports inside of your tests then this startup time will be counted as a part of the test.

    You can avoid that by preloading the scripts, either at the top of your test file or in the setup phase.

    Preloading you script at the top of the file is the simplest way,
    just put import './path'; or require('./path');

    Preloading in the startup phase is only useful if you often run some select tests instead of all test, since you don't want to preload scripts that aren't going to be tested.

    In jest this is done using beforeAll() like so:

    describe('my thing', () =>
    {
        beforeAll(() => import('./path'))
    
        // alternatively you can use require:
        beforeAll(() => require('./path'))
    
        // ...your tests...
    })
    

    I've never used vitest, so I don't know how setup phase is configured there. I've only found this, but that looks like a global config and it's probably better to put the import at the top of your test script then to do it in a global config.


    Side Notes:

    Having a component that takes several seconds to load might indicate that it's importing stuff it doesn't need.
    That often happens when you have a lot of components in the same file or when you don't move shared code into separate files.
    Having a lot of unrelated code in 1 file is a problem for Node because it can't do any tree shaking and it wont skip unused imports.

    Also if your components doesn't always render all of it's sub-components then you can use lazy import for those sub-components as well.