Mocking ES6 module import with and without Dependency Injection

Mocking ES6 module import with and without Dependency Injection

ES6 module system is a great addition to JavaScript language, but sometimes it’s at odds with writing tests, because you need to mock imported function.

Usual solution is to use Dependency Injection akin to what is used in Angular.JS. You need to wrap the whole module into some function and explicitly pass required dependencies. While such approach works fine, it mangles code and adds an additional layer of indirection.

There are two alternative approaches. One is by using mocking library, which is simpler. Another one is more complex and uses Dependency Injection, but in a way, which does not cause big changes to initial code. Using mocking library works fine at the moment, because ES6 modules are transpiled into ES5 objects. But it could not work with real ES6 modules and then Dependency Injection way can be handy.

Mocking with Sinon.JS

Let’s consider feature.js module, which looks like this:

feature.js imports fetchData function from backend.js. The goal is to mock fetchData call in feature.js when writing functional tests. In our example, we will use Sinon.JS, but Jasmine can be used as well.

So, the main trick here is to import the whole module:

and stub/mock required call:

Mocking with Dependency Injection

To manually mock the function, the simplest way would be to reassign fetchData to some mock-function, but imported bindings are read-only. So, we need to make a little trick:

Now, instead of directly using fetchData from backend.js we imported it as originalFetchData. Our local variable fetchData can be reassigned to anything we want. By default it is bound to originalFetchData. To mock it we need to call mock and pass a mocked version. In case we need to unmock, just call mock with no params.

An example of usage in test:

Mocking Module

There is a slight variation of this approach, if we need to mock a set of calls from one module:

Here we import the whole module and cherry-pick required calls. The rest is the same as before, but we need to pass an object with mocked calls into mock.