Skip to content

Tools for testing React components, and Redux actions and containers.

License

Notifications You must be signed in to change notification settings

smaato/react-test-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React Test Kit

These tools make it easier for us to test apps built with React and Redux.

Dependencies

  • Node 4.x.x
  • Browserify with the Babelify transform
  • Jasmine (for some tools, see below)
  • React, react-dom, react-addons-test-utils
  • Redux, redux-thunk, react-redux
  • Sizzle

Getting Started

Setup & Run

npm install
npm install -g gulp
gulp

Usage

Just import the tools you want into your test file:

import {
  ContainerTestCase,
  MockedRouteActions,
  MockedStore,
  mockFormStore,
  TestCase,
  TestCaseFactory,
} from 'react-test-kit';

Tools

TestCaseFactory

Use this factory to test React components' state by passing in various props, inspecting their DOM, and simulating user interactions.

WARNING: Methods for finding child Components won't work unless the root component is a composite Component (e.g. a Component you've defined), not a native Component (e.g. div or table). This seems related to this React issue.

// Construction via factory methods:
it('accepts a ReactElement and returns a TestCase instance', () => {
  const props = {
    property1: 'value1',
  };
  const testCase = TestCaseFactory.createFromElement(<TestElement {...props} />);
  expect(testCase instanceof TestCase).toBe(true);
});

it('accepts a ReactClass and returns a TestCase instance', () => {
  const props = {
    property1: 'value1',
    children: <div>A child</div>,
  };
  // Alternatively, you can use TestCaseFactory.createFromClass here.
  const testCase = TestCaseFactory.create(TestElement, props);
  expect(testCase instanceof TestCase).toBe(true);
});

it('accepts a function and returns a TestCase instance', () => {
  const props = {
    property1: 'value1',
  };
  // Alternatively, you can use TestCaseFactory.createFromFunction here.
  const testCase = TestCaseFactory.create(StatelessTestElement, props);
  expect(testCase instanceof TestCase).toBe(true);
});

// The testCase instance surfaces a method for triggering events and simulating
// user interaction. It supports all of the same events that React supports.
testCase.trigger('click', optDomNode, optEventData);

// You can query its DOM.
const nodes = testCase.find('div');
const node = testCase.first('div');

// You can also query it for React components.
const elements = testCase.findComponents(ChildComponent);
const element = testCase.firstComponent(ChildComponent);

ContainerTestCase

Use this class to test that Redux container classes are providing the right properties and actions.

Note: This class dynamically creates its own Jasmine assertions (so it depends upon Jasmine).

// Given a Container that looks like this:
function mapStateToProps(state) {
  return {
    prop1: state.reducerSource.something,
    prop2: undefined,
  };
}

function mapDispatchToProps(dispatch) {
  const actions = {
    action1: () => {},
    action2: () => {},
  };
  return bindActionCreators(actions, dispatch);
}

const TestContainer = connect(mapStateToProps, mapDispatchToProps)(() => {
});

// You have to pass it into `ContainerTestCase` with a mock state object,
// and then run tests on it, like this:
const testCase = new ContainerTestCase(TestContainer, {
  reducerSource: {},
});

describe('Props', () => {
  testCase.expectProps([
    'prop1',
    'prop2',
  ]);
});

describe('Action creators', () => {
  testCase.expectActionCreators([
    'action1',
    'action2',
  ]);
});

MockedStore

Use this class to test that asynchronous Redux action creators create the right actions in the right order.

it('asserts that asynchronous actions are created in sequence', (done) => {
  const actionCreator = () => (dispatch) => {
    dispatch({
      type: 'A',
    });
    setTimeout(() => {
      dispatch({
        type: 'B',
      });
    }, 10);
  };
  const expectedActions = [{
    type: 'A',
  }, {
    type: 'B',
  }];
  const store = new MockedStore();
  store.expectActionsFromCreator(actionCreator(), expectedActions, done);
});

it('references the mocked state object', (done) => {
  const state = {
    prop: 'value',
  };
  const actionCreator = () => (dispatch, getState) => {
    dispatch({
      payload: getState().prop,
    });
  };
  const expectedActions = [{
    payload: state.prop,
  }];
  const store = new MockedStore(state);
  store.expectActionsFromCreator(actionCreator(), expectedActions, done);
});

mockFormStore

Call this function to get a store instance that uses the redux-form reducer.

Given a component that uses redux-form and looks something like this:

import React, {
  Component,
} from 'react';
import {reduxForm} from 'redux-form';

class AccountForm extends Component {
  // ...
}

AccountForm = reduxForm({
  form: 'accountForm',
  fields: [ /* ... */ ],
})(AccountForm);

export default AccountForm;

You can create a spec that tests it like this:

import React from 'react';
import { Provider } from 'react-redux';
import {
  TestCaseFactory,
  mockFormStore,
} from 'react-test-kit';

import AccountForm from './AccountForm.jsx';

describe('AccountForm', () => {
  it('is testable', () => {
    const store = mockFormStore();

    const props = {
      // ...
    };

    const testCase = TestCaseFactory.createFromElement(
      <Provider store={store}>
        <AccountForm {...props} />
      </Provider>
    );

    // expect() something
  });
});

MockedRouteActions

Use this class to create redux-router actions for testing action creators that affect the route (e.g. redirects).

Note: We've since migrated to react-router-redux so this tool is no longer being supported and will be removed in the near future.

const state = {};
const pathname = '/testPath';
const action = MockedRouteActions.replaceState(state, pathname);
expect(action).toEqual({
  type: '@@reduxReactRouter/historyAPI',
  payload: {
    method: 'replaceState',
    args: [
      state,
      pathname,
    ],
  },
});