taskworld / test-bed

:factory: Development test runner for webpack to improve TDD experience. Runs only specs affected by code change. Serve test files from memory. For large project with hundreds of test files.
33 stars 5 forks source link

test-bed

test-bed is a testing tool that integrates with webpack to provide a better test-driven development experience. It only executes run test files that are affected by the code change.

This project is only meant to improve the developer experience. It is not for running tests inside a CI. For that purpose, you should use something like Karma. (We use both: test-bed for TDD, Karma for CI)

Overview

Once test-bed is set up, you can run it by invoking ./node_modules/.bin/test-bed.

It binds a web server on port 9011 and shows you the test result.

Powered by webpack-dev-middleware, your bundle files are served from memory. No disk writes!

test-bed integrates closely with webpack. Because of this, it can track dependencies between modules, and will re-execute only the changed files.

Powered by webpack-hot-middleware, an overlay will be displayed when there is a bundler error.

If you’ve set up code coverage instrumentation (e.g. using babel-plugin-__coverage__), then test-bed will generate a coverage report file (lcov.info) so that you can integrate coverage measurement into your text editor!

Why?

At Taskworld, our front-end, as well as our test suite, is growing quickly. Now we have hundreds of test files…

We’ve been using Karma with webpack, and there are some pain points:

For running in CI servers, we use Karma which works perfectly fine!

How to use it?

  1. Install test-bed. (Note: Please use Node 6.)

    npm install --save-dev test-bed
  2. Create a webpack.config.test-bed.js file with your webpack configuration.

    • entry should be set to the test entry file. For example, ./test-entry.js.
  3. Create a test entry file, which sets up the testing environment and sends the test context to TestBed:

    // ./test-entry.js
    // This example assumes you are using Mocha test framework,
    // but test-bed should work with any browser-based test framework,
    // as long as it exposes the necessary hooks.
    
    // 1. Set up your test environment. (e.g. mocha, power-assert, chai)
    //    Let’s use an adapter for mocha.
    var TestBedMocha = require('test-bed/adapters/mocha')
    TestBedMocha.setup({ ui: 'bdd' }) // this makes `describe`, `it` available.
    
    // 2. Set up your test environment.
    global.chai = require('chai')
    global.expect = global.chai.expect
    
    // 3. Run test-bed, sending the webpack context.
    TestBedMocha.run({
      // Specify the test context: https://webpack.github.io/docs/context.html
      context: require.context(
        './src',        // ← Look for test files inside `src` directory.
        true,           // ← Recurse into subdirectories.
        /\.spec\.js$/   // ← Only consider files ending in `.spec.js`.
      )
    })
  4. Run ./node_modules/.bin/test-bed and go to http://localhost:9011/

Webpack configuration options

You can change options of the webpack middleware by adding a webpackMiddleware entry to webpack.config.test-bed.js. The following code will restore the default webpack output and enable polling:

// webpack.config.test-bed.js
module.exports = {
  entry: ./test-entry.js
  ... // other webpack options
  webpackMiddleware: {
    quiet: false,
    watchOptions: {
      aggregateTimeout: 300,
      poll: true,
      ignore: /node_modules/
    }
  }
}

Furthermore, you can configure test-bed by adding a testBed entry to your webpack.config.test-bed.js:

// webpack.config.test-bed.js
module.exports = {
  ... // other webpack options
  testBed: {
    openBrowser: true
  }
}

Available options are:

Command line options

Appendix: How it works...

Appendix: Client API

test-bed comes with an adapter for Mocha. But if you want to integrate test-bed with other test frameworks, you can use the client API directly.

const TestBed = require('test-bed')

TestBed.run(options)

This function makes test-bed start running... Here are the options:

Coverage measurement functions

test-bed supports code coverage measurement. However, by default, when not all test files are run, the result code code coverage can be inaccurate (until you run all tests again).

To make code coverage more accurate when running subset of tests, test-bed can record test coverage for each test separately. This is handled automatically in mocha adapter.

When using test-bed API directly, you should call these methods to obtain more accurate coverage data:

See mocha adapter source code for example.

Appendix: How we tripled our test speed with this one weird trick.

As our application grows, we notice that our test starts running slower and slower. We found out that in our React component tests, we mounted the component but didn’t unmount it!

This causes hundreds of components that connects to a several legacy global stores to re-render itself whenever the store triggers. This slows the store’s unit tests drastically — by about ~0.5 seconds per test.

The solution? We monkey-patched ReactDOM so that we can keep track of all the mounted component instances, then we unmount them all before starting the next test. This also forces us to keep all tests isolated.

// spec-helper.js
import ride from 'ride'

const cleanupPreviouslyMountedComponent = (() => {
  let _mountedContainers = [ ]

  ride(ReactDOM, 'render', (render) => function (element, node) {
    const component = render.apply(this, arguments)
    _mountedContainers.push(node)
    return component
  })

  return () => {
    const containersToCleanUp = _mountedContainers
    if (!containersToCleanUp.length) return
    for (const container of containersToCleanUp) {
      try {
        ReactDOM.unmountComponentAtNode(container)
      } catch (e) {
        console.error('[spec-helpers] Cannot unmount component:', e)
      }
    }
    _mountedContainers = [ ]
  }
})()

beforeEach(function () {
  cleanupPreviouslyMountedComponent()
})