Test module.hot with Jest

Viewed 1362

I'm trying to get coverage to 100% for a module with hot module reloading setup.

In my module I have this:

// app.js
if (module && module.hot) module.hot.accept();

In the test file I am trying to do this

// app.test.js
it('should only call module.hot.accept() if hot is defined', () => {
    const accept = jest.fn();
    global.module = { hot: { accept } };
    jest.resetModules();
    require('./app');
    expect(accept).toHaveBeenCalled();
  }
);

But when I log out module in app.js it shows the require stuff but doesn't contain the hot method set by test.

2 Answers

If you have a variable referencing the module object, then you can inject a mock module object into that variable for the test. For instance, you could do the following:

// app.js

// ...
  moduleHotAccept(module);
// ...

export function moduleHotAccept(mod) {
  if (mod && mod.hot) {
    mod.hot.accept();
  }
}

Which can be tested like so:

// app.test.js
import { moduleHotAccept } from './app'

it('should only call hot.accept() if hot is defined', () => {
    const accept = jest.fn();
    const mockModule = { hot: { accept } };
    moduleHotAccept(mockModule);
    expect(accept).toHaveBeenCalled();
  }
);

it('should not throw if module is undefined', () => {
    expect(moduleHotAccept).not.toThrow();
  }
);

it('should not throw if module.hot is undefined', () => {
    expect(
      () => moduleHotAccept({notHot: -273})
    ).not.toThrow();
  }
);

I've needed it as well without the ability to pass it from outside.

My solution was to use a jest "transform" that allows me to modify a bit the code of the file that is using module.hot.

So in order to setup it you need to add:

// package.json

"transform": {
  "file-to-transform.js": "<rootDir>/preprocessor.js"
//-------^ can be .* to catch all
//------------------------------------^ this is a path to the transformer
},

Inside preprocessor.js,

// preprocessor.js

module.exports = {
  process(src, path) {
    if( path.includes(... the path of the file that uses module.hot)) {
      return src.replace('module.hot', 'global.module.hot');
    }

    return src;
  },
};

That transformer will replace module.hot to global.module.hot, that means that you can control it value in the tests like so:

// some-test.spec.js

global.module = {
  hot: {
    accept: jest.fn,
  },
};

Hope that it helps.

Related