Adding mocking to React.js Unit Tests

I am continuing to build up my set of templates to improve the tools used to develop, test and build React.js front-ends (see original article introducing my ASP.NET React.js templates). In my first iteration I created a project to contain my Unit Tests, but it only had five very basic tests. In this article I describe how I beefed up the Unit Tests, especially around mocking out ES6 modules.

This article is the first article in the series I am writing about creating a React.js samples that are a good starting point for real-world, production-ready applications with full React.js build, test, and deploy capabilities. The other articles in the series are:

  1. Templates for building React.js front-ends in ASP.NET Core and MVC5
  2. Using a Redux store in your React.js application
  3. This article: Adding mocking to React.js Unit Tests
  4. Unit Testing React components that use Redux

The aims of this article

  • Why I chose the JavaScript mocking approach called inject-loader.
  • A detailed summary of how to use inject-loader, as it wasn’t totally obvious to me.
  • A quick aside on mocking JavaScript Promises.
  • Links an open-source project which contains an example of Unit Testing with Mocks.

I’m going to assume you at least know what React is and what Unit Testing is and why Unit Testing is useful. If you don’t know React try these article links [1, 2, 3]. I would also really recommend the book “Pro React”, which helped me a lot. On Unit Testing  and mocking try this overview article.

Note: One word of warning:  React.js is still changing and articles and books get out of date. This article and open-source AspNetReactSamples on GitHub uses React version 15.0.2 (see package.json for full list).

Setting the scene

I described my Unit Test environment in the first article, but in summary I am using the Mocha test framework run by the Karma test runner (read section 3 of the original article for why I, and others, don’t use the suggested Jest test framework). The whole React.js build environment uses Babel and WebPack extensively.

I had recently converted one of my React web apps over to use Redux and I wanted to create some proper Unit Tests. The React.js application, called Kanban (comes originally from the book “Pro React” book), uses async accesses to the server to fetch and save the data. This was pretty fundamental to how Kanban works and I needed to intercept this if I was going to test certain parts of the code.

I could have used a specific library to intercept the “fetch”, but I also needed to mock some other parts so I wanted a generic way of replacing ES6 modules with some stub or test code – this is called mocking.

Down the rabbit hole (again!)

In the first article I said that when I researched setting up the build process it “felt like I had fallen down Alice in Wonderland’s rabbit hole!”. Well, it felt the same when I tried to find a method to mock ES6 modules (but thankfully not quite as deep as with Webpack!).

I found this stackoverflow question and answers, which mentioned a number of ways, like rewire/babel-plugin-rewire library,  proxyquire and inject-loader. I spent a frustrating day trying to get babel-plugin-rewire to work to no avail before I swapped to try inject-loader.

Thankfully I found an excellent article called ‘Mocking ES6 import for Tests‘ by James Tease. This looked like just the thing, especially as he gives an example which uses Redux.

True be told I was still a bit confused and couldn’t get inject-loader to work. I didn’t find the inject-loader Readme file totally enlightening as I thought I needed to link in the inject-loader into my karma/webpack files (you don’t – all you need to do is load inject-loader, which you do by adding “inject-loader”: “2.0.1” to your package.json and updating your packages).

Mocking example

In the end I created a really simple test of mocking to check I understood inject-loader. First I will list three small modules that I created for the test. You will see that the final module, called ‘OuterFunction’, imports and used the other two (you can find the actual files in this directory)

//file InnerFunction.js
export default function inner() {
    return 'Inner Function';
}
//file InnerValue.js
export default 42;
//file OuterFunction.js
import InnerFunction from './InnerFunction';
import InnerValue from './InnerValue';

export default function outer() {
    return {
        innerFuncValue: InnerFunction(),
        innerValue: InnerValue
    }
}

This is a very contrived example, but they really helped me understand how to use inject-loader. Here is a section of my mocha test called ‘TestMocking.test.js‘:

import expect from 'expect';

import OuterFunction from '../../localSrc/OuterFunction';
import innerMock from '../../mocksEtc/MockInnerFunction'

describe('Test Mocking example', function () {

    it('check normal operation works',
    () => {
        var result = OuterFunction();
        expect(result.innerFuncValue).toEqual('Inner Function');
        expect(result.innerValue).toEqual(42);
    });

    it('mock InnerFunction with local function',
    () => {
        const localFunc = () => { return 'local mock'};
        const inject = require('inject?./InnerFunction!../../localSrc/OuterFunction');
        const outerFunctionWithMock = inject({
                './InnerFunction': localFunc
            }).default;
        var result = outerFunctionWithMock();
        expect(result.innerFuncValue).toEqual('local mock');
        expect(result.innerValue).toEqual(42);
    });
    //... lots more tests

The key lines are 18 to 21 so let me take you through each part as this is where I got confused.

Line 18 – the require statement

In one of the emails with James Tease he said using inject-loader “look weird” if you are used to ES6 imports. You can see what he means when you look at line 18. It is odd to see a require statement in ES6 code, but that is how inject-loader works, i.e. it alters the module as you load it. The format of the require parameter string is important so let me go through it in detail, with bold used to show each part:

  1. inject?./InnerFunction!../../localSrc/OuterFunction’
    The start of the string makes the inject-loader take over the load of the module.
  2. ‘inject?./InnerFunction!../../localSrc/OuterFunction’
    This says which module inside the ‘OuterFunction’ that we want to replace. It starts with the question mark followed by the EXACT same string used in the import statement in ‘OuterFunction’. Note: this part is optional – you can leave it out if you plan to replace ALL of the imports (example later).
  3. ‘inject?./InnerFunction!../../localSrc/OuterFunction
    This says what module you want to load, in this case the ‘OuterFunction’ module. The string starts with an exclamation mark and then has the correct file reference to access the module from where the test is being run.

Be warned: if you get part three wrong then you will get an error, but if you get part 2 wrong then it just doesn’t work.

Lines 19 to 21 – mock out the module(s)

This is the part that replaces the imported module inside the module you are loading. In this example we are replacing the ‘InnerFunction’ module inside the ‘OuterFunction’ module. Again there a a few things to watch out for:

  • line 20. Replacing a module.
    The content of the string ‘./InnerFunction’ is again crucial. It must be the EXACT same string used in the import statement inside the module you are loading, in this case the ‘OuterFunction’ module. The part after the colon is the new content. You can give it a whole module, or in this case just a function as that is all the ‘InnerFunction’ contains.
    Note: Do have a look at James Tease’s example ‘Mocking ES6 import for Tests‘ as he sends over an object. My TestMocking example Unit Test shows different ways to replace a module.
  • Line 21. getting the right exports from the loaded module
    You have loaded a module using require. Because of this you need to know how to access the parts that this module exports. If its an ES6 module then there is a convention for that. In the case of the ‘OuterFunction’ module it exports just one function as its default: therefore I can access it by adding .default to the end of the statement.  James Tease gives an example of accessing a specific class in his article, Mocking ES6 import for Tests.

More examples

There are lots of different ways you can use inject-loader. I have copied the following section from the inject-loader Readme file, so you can see things all in one place.

// If no flags are provided when using the loader then
// all require statements will be wrapped in an injector
MyModuleInjector = require('inject!MyStore')
MyModule = MyModuleInjector({
  'lib/dispatcher': DispatcherMock,
  'events': EventsMock,
  'lib/handle_action': HandleActionMock
})

// It is also possible to only mock only explicit require
// statements via passing in their path as a flag
MyModuleInjector = require('inject?lib/dispatcher!MyStore')
// only 'lib/dispatcher' is wrapped in an injector
MyModule = MyModuleInjector({'lib/dispatcher': DispatcherMock})

// this also works for multiple flags and requires
MyModuleInjector = require('inject?lib/dispatcher&events!MyStore')
// only 'lib/dispatcher' and 'events' are wrapped in injectors
MyModule = MyModuleInjector({
  'lib/dispatcher': DispatcherMock,
  'events': EventsMock
})

// you can also explicitly exclude dependencies from being injected
MyModuleInjector = require('inject?-lib/dispatcher!MyStore')
// everything except 'lib/dispatcher' is wrapped in an injector
MyModule = MyModuleInjector({
  'events': EventsMock,
  'lib/handle_action': HandleActionMock
})

To reiterate, it is important to get the strings in the right format:

  • When referring to the module you want to replace then you must use the EXACT file reference used in the import inside the module you are loading. If you get this wrong it doesn’t error, but it just doesn’t work.
  • The file reference for the module you are loading should be the correct reference to reach the module from where your test is located in the file system. If you get this wrong then you get the normal “file not found” type error.

Also the format of the inject has to match what the calling module expects. For mocking out simple modules that use a default return then you can just inject something that matches that default, e.g. a function in the case of my ‘InnerFunction’ example. For mocking out more complex modules with multiple exports then its often easer to create a mock of the module.

Note: Have a look at my file  ‘TestMocking.test.js‘ which has some really simple examples. If you want to see an example of the mocking used for real Unit Testing then have a look at CardActionCreators.tests.js, which tests the ActionCreators for a card in the Kanban app.

Quick Aside – mocking Promises

My main aim was to test some of the code that accessed data on the server via the “fetch” library. The “fetch” command is async and uses JavaScript Promises. I looked at how to mock a promise and found these articles [1, 2]. I tried a few approaches that these two articles suggested, but it definitely wasn’t simple. As Jani Hartikainen says in his article,  “dealing with promises in unit tests is a hassle“.

In the end I looked at how Promises were used in the kanban application and found it only used the then(success, failure) function of the Promise. I therefore created a really simple MockPromise that just has a just the then function. You can see me using the MockPromise in the ReduxDispatcher.test.js and CardActionCreators.tests.js.

Conclusion

I am slowly improving the dev, test, build tools in my React samples. In this article I have improved my ability to Unit Test a larger amount of my code base by adding mocking. Finding the right way to do that was a bit of a struggle as the tools, e.g. Babel, Webpack etc. are changing so some articles you read are out of date.

I do like the inject-loader but its syntax is not obvious and there isn’t a lot of documentation on how to use it. Hopefully this article and the associated example code will help you to understand and use the inject-loader to make your Unit Test more effective. Please feel free to point out other mocking methods that have worked for you, preferably with some example code that I and others can look at.

Happy coding.