Markus Bergh

Testing React components in the browser with Karma

Testing React components in the browser with Karma

When testing your React components, you probably use a framework or utility that helps you, such as Jest. Or Mocha. Maybe you run them headless with JSDom.

Although it can be effective to run your tests in a headless environment, sometimes it would be easier to debug in the browser with full source maps. And with Karma, we can achieve that.

I will be setting up Karma with a test stack of Enzyme (great test utility for manipulating React components), Mocha and all JavaScript is preprocessed with Webpack. For assertion I am using chai. I assume that you have some understanding of Webpack and Babel and will not go through the process of getting those up and running. I be using Webpack 2.x.

Dependencies

Making it work requires a few libraries:

  • karma (test runner)
  • karma-webpack (preprocess sources for Karma)
  • karma-mocha (using the test framework Mocha with Karma)
  • karma-sourcemap-loader (plugin for loading the source maps into Karma)
  • karma-chrome-launcher (plugin for starting up Chrome, use other launchers for different browsers)
  • mocha (our test framework)
  • chai (for writing assertion)

And when working with React you normally have these (among others):

  • react
  • webpack
  • babel-cli (includes babel-node for transpiling code and running it)
  • babel-loader (for transpiling with Babel and Webpack)

Configuration

For Karma we need to create a karma.conf.js, that will live in the application root.

// karma.conf.js

import path from 'path';

export default (config) => {
  config.set({
    frameworks: ['mocha', 'chai'], // Which frameworks we want to use
    client: {
      mocha: {
        // Will output running test in a prettified style, as explained below,
        // otherwise output will be in the browser console and as always
        // in the terminal console
        reporter: 'html'
      }
    },
    files: [
      'karma-helper.js', // Will set some global variables for us
      'src/**/*.spec.js' // The actual test files
    ],
    preprocessors: { // Preprocess with our plugins for Webpack and source maps
      'karma-helper.js': ['webpack', 'sourcemap'], 
      'src/**/*.js': ['webpack', 'sourcemap'],
      'src/**/*.spec.js': ['webpack', 'sourcemap']
    },
    webpack: { // Most likely a copy of your Webpack configuration
      devtool: 'inline-source-map', // Do inline source maps instead of the default
      resolve: {
        modules: [
          'node_modules',
          './src'
        ],
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: path.resolve(__dirname, 'node_modules'),
            loader: 'babel-loader',
            options: {
              presets: ['react', 'es2015', 'stage-0']
            },
          }
        ],
        ...
      },
      externals: { // Depending on which React version you will need to add this, if you use Enzyme, to have them ignored
        'react/addons': true,
        'react/lib/ExecutionEnvironment': true,
        'react/lib/ReactContext': true
      },
    },
    webpackServer: {
      noInfo: true // Do not output in the console when running in Karma
    },
    plugins: [ // Load all plugins we are using
      'karma-webpack',
      'karma-chai',
      'karma-mocha',
      'karma-sourcemap-loader',
      'karma-chrome-launcher'
    ],
    reporters: ['progress'], // More reporters are available as plugins
    port: 4321, // Which port Karma should run on, default is 9876
    colors: true,
    logLevel: config.LOG_INFO,
    browsers: ['Chrome'], // What browser(s) we are launching
    singleRun: false // This makes Karma not end when the tests are finished running
  });
};

You can read more about what's available to set in the configuration here.

To make things easier we set some globals in a helper file.

// karma-helper.js

import { shallow, render, mount } from 'enzyme';
import React from 'react';

global.React = React;
global.shallow = shallow;
global.render = render;
global.mount = mount;

Run the tests in the browser

Now that we have written our configuration in ES6, we need to run it through Babel to make it work.

// package.json
{
  ...
  "scripts": {
    // It will not work without the relative path to the Karma binary
    "test:karma": "babel-node --presets es2015 -- ./node_modules/.bin/karma start"
  }
  ...
  "devDependencies": {
    // You will need this library in your devDependencies,
    // so make sure to install it too and use --save-dev
    "react-test-renderer": "^15.6.1"
  }
  ...
}

Here is an example of a component test that we will run.

// src/components/Button.js

import React from 'react';
import PropTypes from 'prop-types';

export const _Button = ({
  children
}) => (
  <button>
    {children}
   </button>
);

_Button.propTypes = {
  children: PropTypes.node.isRequired
};

export default _Button;
// src/components/Button.spec.js

import { _Button as Button } from './Button';

describe('Button', () => {
  let wrapper;

  before(() => {
    wrapper = shallow(
      <Button>
        Child
      </Button>
    )
  });

  it('should display the children it received', () => {
    expect(wrapper.html()).to.contain('Child');
  });
});

Now that you have installed all dependencies, and set up configuration, you can now run npm run test:karma. The launcher will open Chrome automatically and the tests will run through, as you can see in the console.

> karma-test@1.0.0 test:karma /Users/markusbergh/Projects/karma-test
> babel-node --presets es2015 -- ./node_modules/.bin/karma start

22 08 2017 14:37:52.860:WARN [karma]: No captured browser, open http://localhost:4321/
22 08 2017 14:37:52.876:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:4321/
22 08 2017 14:37:52.876:INFO [launcher]: Launching browser Chrome with unlimited concurrency
22 08 2017 14:37:52.932:INFO [launcher]: Starting browser Chrome
22 08 2017 14:37:54.281:INFO [Chrome 60.0.3112 (Mac OS X 10.12.6)]: Connected on socket AhA5FIvkuaLFR1QuAAAA with id 66795235
Chrome 60.0.3112 (Mac OS X 10.12.6): Executed 1 of 1 SUCCESS (0.019 secs / 0.006 secs)

To see a prettier output, go to /debug.html or click the "Debug" button, and now you can also open the developer tools and set breakpoints to easily debug the tests.

Since Karma is watching by default, your changes will be noticed and tests will execute again when updating your code.