Search code examples
unit-testingjestjsmocking

Only mock an import if it can't be found in jest?


The code base I work on is built by different teams who have access to different proprietary packages.

Normally this is fine, but if I want to make sure my code changes don't break for their builds, I need to build it as them and run the shared tests.

Here's a dummy example:

// in SharedComponent.tsx
import { ChildComponent } from 'variations/foo-bar';

const SharedComponent = () => <><ChildComponent /></>;

And the contents of variations/foo-bar/ChildComponent vary based on how the code is built (in essence variations/foo-bar is replaced by variations-team-awesome/foo-bar when built by 'team-awesome' and variations-team-amazing/foo-bar when build by 'team-amazing').

Here is an example:

// in variations-team-awesome/foo-bar/ChildComponent.tsx
import { DoThings } from '@proprietary-X/things'

const ChildComponent = () => <div>{DoThings()}</Div>
// in variations-team-amazing/foo-bar/ChildComponent.tsx
import { DoStuff } from '@proprietary-Y/stuff'

const ChildComponent = () => <div>{DoStuff()}</Div>

If I build the SharedComponent for my jest tests as 'team-awesome' but their version of ChildComponent imports something proprietary I don't have access to, then jest will fail saying it can't find the module.

However, if the imports in ChildComponent are always mocked, then it won't get tested as part of SharedComponent (assume it makes sense to not mock them in these examples).

How can I conditionally mock these imports (i.e. @proprietary-Y/stuff and @proprietary-X/things) for the different teams?


Solution

  • Forgiveness is easier than permission

    In this case, it's better to try and import, and only mock if it fails.

    This works in either the test file itself:

    // manual mock in a function
    jest.mock(
      '@proprietary-X/things',
      () => {
        try {
          return jest.requireActual('@proprietary-X/things');
        } catch {
          console.log('module not found, mocking...');
        }
        return {
          DoThings: () => 'mocked DoThings',
        }
      },
      { virtual: true },
    );
    

    n.b. virtual is needed when the mock is for a module that doesn't exist (which is won't depending on the team).

    Or using __mocks__ folders (with a helper function):

    const conditionallyMock = (module, mock = {}) => {
        try {
          return jest.requireActual(module);
        } catch {
          console.log(`module '${module}' not found, mocking...`);
        }
        return mock;
    }
    
    // in __mocks__/@proprietary-X/things
    module.exports = conditionallyMock('@proprietary-X/things', {
      DoThings: () => 'mocked DoThings',
    });
    // in __mocks__/@proprietary-Y/stuff
    module.exports = conditionallyMock('@proprietary-Y/stuff', {
      DoStuff: () => 'mocked DoStuff',
    });
    

    This should work smoothly for both teams, and only console log out module '${module}' not found, mocking... when you locally build as the other team and run the shared tests.