How to mock es2015/ECMAScript 6 Classes with Jest and Babel-Jest

If you are using babel to compile your app's es2015/ecmascript 6 source to ecmascript 5, and you are using Jest for your unit tests, you will have some additional challenges writing your tests with Jest due to its JavaScript 5 roots.

Jest Setup for ES2015/JavaScript 6 Source

To use Jest for your es2015 source you need to configure package.json for Jest and babel-jest.

    "scripts": {
        "test": "jest"
    },
    "jest": {
        "scriptPreprocessor": "/node_modules/babel-jest",
        "preprocessCachingDisabled": true,
        "unmockedModulePathPatterns": [
            "/node_modules/react",
            "/node_modules/react-dom",
            "/node_modules/react-addons-test-utils",
            "/node_modules/fbjs",
            "/node_modules/cauldron-mvc"
        ],
        "testFileExtensions": [
            "es6",
            "js"
        ],
        "moduleFileExtensions": [
            "js",
            "json",
            "es6"
        ]
    },

You may not need all the entries under unmockedModulePathPatterns. The listing above is if you are using react and the cauldron-mvc framework. Any modules listed under unmockedModulePathPatterns will not be automocked by Jest.

What is crucial here, for compiling your code to JavaScript 5 is the scriptPreprocessor configuration. You will also need to ensure you have the following dev dependencies:(these are the versions I am using at time of writing.)

        "jest": "^0.1.40",
        "jest-cli": "^0.8.2",
        "babel-jest": "^6.0.1",
        "babel-preset-es2015": "^6.3.13",
        "babel-preset-react": "^6.3.13",
        "babel-preset-stage-1": "^6.3.13"

Configure babel-jest

What you definitely need, to compile your es2015, JavaScript 6 source to JavaScript 5, is the babel-jest preprocessor, which we configures in package.json above. But this alone is not enough, if you are using any babel presets. You need to ensure that any babel presets you may be using are picked up by babel-jest.

In your gulpfile.js you can pass these preset in the gulpfile.js itself but these configurations won't be picked up by "npm test". You will need to add the presets to your .babelrc file, in your top-level directory of your project. These entries should match what babel presets you have in your grunt of gulp tasks.

{
    "presets": ["es2015", "react", "stage-1"]
}

Writing your Jest Test Suite for JavaScript 6 Source

At time of writing it is not possible to write your tests in JavaScript 6 syntax in any sane way.  Trying to use "import" to get your classes imported into the test will cause problems if you use jest.dontMock statements in the test suite file. This is due to hoisting of the import statement so if you have something like.

         jest.dontMock('../src/MyModel');
         import MyModel from '../src/MyModel';

You will end up importing MyModel before the dontMock statement so it will be mocked by the time the dontMock statement is read. One way around this is to put your jest.dontMock statements into a separate file, like do-not-mock.js and import that with "import '../test/do-not-mock'". But this does not solve your other problems. that will arise with JavaScript 6 classes and Jest.

Jest and JavaScript 6 Class Syntax

As Jest is not yet fully compatible with es2015 there are issues if you try and provide mocked method implementations to a mocked class.

If you have a es2015 class as follows:

'use strict';

class MyModel{
    
    constructor(){
        console.log("constructor");
    }
    
    myMethod(){
        console.log("this is my method");
        return "mymethod called";
    }
    
}

export default MyModel;

Your test suite will need to look as follows:

'use strict';
//import MyModel from '../public/MyModel'; <- this will not work

var MyModel = require('../public/MyModel').default;
MyModel.prototype.myMethod = jest.genMockFn();
MyModel.prototype.myMethod.mockImplementation(function () {
    return "mymethod called";
});

describe("test func", function () {
    it('should mock', function () {
        var model = new MyModel();
        expect(model.myMethod()).toEqual('mymethod called');
    });
});

You need to use the older JavaScript 5 and common.js syntax to import your class. Note the need to reference the "default" property of the required module if you used "export default" for your class.

Conclusion

Thanks to @cpojer, Jest maintainer on #jest for the assistance with getting my tests running. I am sure that Jest will be updated to handle  this is a much more friendly way soon.