Search code examples
reactjsjestjslazy-loadinglazy-evaluation

Lazy-load imports from barrel file in Jest


I'm using React and have a file tree like this:

src/
  common/
    common-comp1/
      common-comp1.js
      common-comp1.test.js
      index.js
    common-comp2/
      common-comp2.js
      common-comp2.test.js
      index.js
    index.js
  app/
    app-comp1/
      app-comp1.js
      app-comp1.test.js
      index.js
    app-comp2/
      app-comp2.js
      app-comp2.test.js
      index.js
    index.js

All of the index.js files are barrel files that export * from '...';. I have path aliases set up so that I can import CommonComp1 from 'common'; in src/app/app-comp1/app-comp1.js.

Unfortunately, this loads/transforms the code from src/common/common-comp2/* as well, even though app-comp1.js doesn't import it. This is fine when I build the app with webpack, because I need everything to be imported and transformed anyway.

However, with tests, with some 350 components, this makes running a single test suite very slow on startup. I only want to import/transform the files necessary to run the tests. Is there a way to lazy-load every export/import? I thought maybe I could do this by mocking my barrel files and exporting a Proxy that only requires/memoizes components when they are imported directly, by calling jest.requireActual(). I started to do this, but then realized I'd have to parse the entire file tree for import and export names, which sounds tedious. (At least I think I would have to do this.)

I thought I might also try proxying require() itself (I attempted this several weeks ago for a different issue), but iirc, it is a constant or declared non-configurable/non-writable.

Thoughts?


Solution

  • You can mock the barrel file in your test using the module factory parameter, and set only the exports you want.

    if common-comp1.js exports as default do it like this:

    app-comp1.test.js:

    jest.mock('common',()=>{
      const CommonComp1 = jest.requireActual('common/common-comp1').default;
      return {__esModule: true, CommonComp1}
    })
    

    if common-comp1.js exports are named then do it like this:

    app-comp1.test.js:

    jest.mock('common',()=>{
      const allNamedExports = jest.requireActual('common/common-comp1');
      return {__esModule: true, ...allNamedExports}
    })
    

    for a more automated version you could have __mocks__/index.js files generated by a script.

    that script would go through each index.js file import it, get the name of the exports and put them in a module.exports object getter.

    Like this:

    __mocks__/common.js:

    module.exports={
        get CommonComp1() { 
            return jest.requireActual('../common/common-comp1').CommonComp1;
        }
        get OtherExportFromCommonComp1() { 
            return jest.requireActual('../common/common-comp1').OtherExportFromCommonComp1;
        }
        get CommonComp2() { 
            return jest.requireActual('../common/common-comp2').CommonComp2;
        }
    }