Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock dynamic require in Node with Jest

Given an npm package that needs to dynamically load a dependency from the root of the parent/referencing package, and that location isn't known until runtime, it has to do a dynamic require:

// config-fetcher.js
const path = require('path');
const getRunningProjectRoot = require('./get-running-project-root');'
module.exports = filename =>
   require(path.resolve(getRunningProjectRoot(), filename));

(There's no guarantee that the module will be in node_modules. It could be symlinked or loaded globally. So it can't use a static require.)

This is simplified from the real code, so unless you know of a way to require files non-dynamically relative to the running project root, this has to be this way.

Now, to test this, I'd prefer not to depend on any file that's actually on disk. However, Jest seemingly won't let you mock a nonexistent file. So if I try this:

const mockFileContents = {};
jest.mock('/absolute/filename.blah', () => mockFileContents);
// in preparation for wanting to do this:
const result = require('./config-fetcher')('/absolute/filename.blah');
expect(result).toBe(mockFileContents);

then I get an error from jest-resolve, with file Resolver.resolveModule throwing Error: Cannot find module '/absolute/filename.blah'.

I need to test some of the functionality of this dynamic requiring module, as it handles some cases of relative paths vs. absolute paths, and allows you to specify a special path through a Symbol, with one being for example applicationRoot, so the module config-fetcher does the hard work instead of the caller.

Could anyone offer guidance on how to test this module, or how to restructure so dynamic requires aren't needed or they are easier to test?

like image 500
ErikE Avatar asked May 09 '19 04:05

ErikE


2 Answers

You can pass { virtual: true } as options in jest.mock to mock a module that does not exist:

const { myFunc } = require('does not exist');

jest.mock('does not exist',
  () => ({
    myFunc: () => 'hello'
  }),
  { virtual: true }
);

test('mock file that does not exist', () => {
  expect(myFunc()).toBe('hello');  // Success!
});

Details

Jest completely takes over the require system for the code under test.

It has its own module cache and keeps track of module mocks.

As part of this system, Jest allows you to create mocks for modules that do not actually exist.

You can pass options as the third parameter to jest.mock. Currently the only option is virtual, and if it is true then Jest will simply add the result of calling the module factory function to the module cache and return it whenever it is required in the code under test.

like image 160
Brian Adams Avatar answered Oct 14 '22 05:10

Brian Adams


Anyone tried testing dynamic import? Below sample codes to test. If I'm not using dynamic import jest.mock + virtual: true works.

const Routes =  React.lazy(() => import('app2/routes'));

jest.mock('does not exist',
  () => ({
    myFunc: () => 'hello'
  }),
  { virtual: true }
);

test('mock file that does not exist', () => {
  expect(myFunc()).toBe('hello');  // Failed!
});
like image 3
Brendon Avatar answered Oct 14 '22 07:10

Brendon