Search code examples
facebookreactjswebpackecmascript-6jestjs

How to use Jest with React and Webpack


I was trying to configure Jest to work with React and Webpack. With the way I use Webpack aliases and ES6 modules exports, I had huge problems configuring it.

Since no other questions worked for me, I want to show you what I did to be able to use Jest with ReactJs and Webpack.


Solution

  • My mainly problem was that I was using ES6 to export my modules and I needed a preprocessor to handle the imports.

    Given that basic component:

    import 'Incription.scss';
    
    import React from 'react;
    import InscriptionModal from './InscriptionModal';
    
    class Inscription extends React.Component {
        constructor(props) {
            super(props)
        }
        render() {
            return <InscriptionModal />;
        }
    }
    
    export default Inscription;
    

    Test looks like:

    import React from 'react';
    import renderer from 'react-test-renderer';
    
    import Inscription from './Inscription';
    
    describe('Inscription', () => {
        it('renders correctly', () => {
            const tree = renderer.create(<Inscription />).toJSON();
    
            expect(tree).toMatchSnapshot();
        });
    });
    

    With no configuration in package.json, when I was running the test I always ended up with:

    SyntaxError: Unexpected token .
    

    Basically, I needed to handle asset files and, to do so, I needed to change my package.json jest config using moduleNameMapper.:

    "jest": {
        "moduleNameMapper": {
          "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/cfg/mocks/fileMock.js",
          "^.+\\.(css|scss)$": "<rootDir>/cfg/mocks/styleMock.js"
        }
    }
    

    fileMock.js

    module.exports = 'test-file-stub';
    

    styleMock.js

    module.exports = {};
    

    Next problem was that Jest could not handle the modules of ES6 so, I needed a preprocessor. The exact error was:

    Cannot find module './InscriptionModal' from 'Inscription.spec.js'
    

    I found a plugin called jest-webpack-alias that it was doing that so, I changed again the jest configuration of my package.json:

    "jest": {
        ...
        "transform": {
            "^.+\\.js$": "<rootDir>/cfg/preprocessor.js"
        },
        "jest-webpack-alias": {
            "configFile": "cfg/jest.js"
        },
    }
    

    And my preprocessor.js file looked like:

    const babelJest = require('babel-jest');
    
    require('babel-register');
    const webpackAlias = require('jest-webpack-alias');
    
    module.exports = {
      process: function (src, filename) {
        if (filename.indexOf('node_modules') === -1) {
          src = babelJest.process(src, filename);
          src = webpackAlias.process(src, filename);
        }
        return src;
      }
    };
    

    Notice the configFile of jest-webpack-file in my package.json. It has a route of my webpack test configuration. The file looked like this:

    'use strict';
    
    const path = require('path');
    
    const srcPath = path.join(__dirname, '/../src/');
    const baseConfig = require('./base');
    
    module.exports = {
      devtool: 'eval',
      module: {
        loaders: [{
          test: /\.(png|jpg|gif|woff|woff2|css|sass|scss|less|styl)$/,
          loader: 'null-loader'
        }, {
          test: /\.(js|jsx)$/,
          loader: 'babel-loader',
          include: [].concat(
            baseConfig.additionalPaths,
            [
              path.join(__dirname, '/../src'),
              path.join(__dirname, '/../test')
            ]
          )
        }, {
          test: /\.json$/,
          loader: 'json-loader'
        }]
      },
      resolve: {
        root: path.resolve(__dirname, '/../src'),
        extensions: [
          '',
          '.js',
          '.jsx'
        ],
        alias: {
          actions: `${srcPath}/actions/`,
          components: `${srcPath}/components/`,
          sources: `${srcPath}/sources/`,
          stores: `${srcPath}/stores/`,
          services: `${srcPath}/services/`,
          styles: `${srcPath}/styles/`,
          i18n: `${srcPath}/i18n/`,
          config: `${srcPath}/config/test`,
          utils: `${srcPath}/utils/`,
          vendor: `${srcPath}/vendor/`,
          images: `${srcPath}/images/`
        }
      }
    };
    

    It's very important in this file to add the root of your project because if not, you will have this error:

    Missing setting "resolve.modules" (webpack v2) or "resolve.root" (webpack v1) in...
    

    With the configs added, you now can mock your imports using Jest mock:

    jest.mock('./InscriptionModal, () => {
      return {};
    });
    

    It's important to add the param --no-cache when running tests because Jest caches transformed module files to speed up test execution.

    Another annoying bug is when you're trying to add enzyme or React Test Utils in your tests. It prints this error:

    Invariant Violation: ReactCompositeComponent: injectEnvironment() can only be called once.
    

    What you have to do is add in your tests this line of code:

    jest.mock('react/lib/ReactDefaultInjection');
    

    I hope you find useful!

    UPDATE

    Upgrading React and React-dom to v15.4.0 fixes latest bug commented.