Skip to content

Instantly share code, notes, and snippets.

@tpai
Last active January 8, 2016 05:59
Show Gist options
  • Save tpai/f59970e6a46a491087a8 to your computer and use it in GitHub Desktop.
Save tpai/f59970e6a46a491087a8 to your computer and use it in GitHub Desktop.

layout: true class: center, middle, inverse

Redux Unit Test

.footnote[tonypai present]

What should I do?

layout: false class: inverse

Preparation

.babelrc

{
  "stage": 0,
  "env": {
    "test": {
      "plugins": [
        "babel-plugin-rewire"
      ]
    }
  }
}

Ref: babel-plugin-rewire

test/setup.jsx

import { jsdom } from "jsdom";

global.document = jsdom("<!doctype html><html><body></body></html>");
global.window = document.defaultView;
global.navigator = global.window.navigator;

Ref: setState in shallowRender fails due to missing document


class: inverse

Preparation

package.json

{
    "scripts": {
        "test": "NODE_ENV=test NODE_PATH=. mocha --compilers js:babel-core/register --recursive 'test/**/*.test.jsx' --require test/setup.jsx",
    },
    "devDependencies": {
        "babel-plugin-rewire": "^0.1.22",
        "deep-freeze": "0.0.1",
        "expect": "^1.13.0",
        "expect-jsx": "^2.1.3",
        "jsdom": "^7.0.2",
        "mocha": "^2.3.4",
        "nock": "^3.1.1",
        "react-addons-test-utils": "^0.14.3"
    }
}
$ npm i babel-plugin-rewire deep-freeze expect expect-jsx jsdom mocha nock react-addons-test-utils --save-dev

layout: false .left-column[

What needs to be tested?

] .right-column[ Redux Directory Structure

  • Actions
User actions and async requests
  • Reducers
Business logic
  • Components (a.k.a Dumb|Presentational Component)
Simply render elements
  • Containers (a.k.a Smart Component)
Pass state data and each handler as props to components

Ref: Definition of containers and components

]

.left-column[

What needs to be tested?

- Actions

] .right-column[

import nock from "nock";
import expect from "expect";
import thunk from "redux-thunk";
import configureStore from "redux-mock-store";
const mockStore = configureStore([ thunk ]);

describe("Action::ACTIONS", () => {
    it("#ASYNC_METHOD()", done => {
        // HTTP mocking
        nock(`API_URL`)
            .get(`/PATH`)
            .reply(HTTP_CODE, MOCK_RESPONSE)
        // expected result
        const expectedActions = [{
            type: ACTION_TYPE,
            ACTION_VALUES...
        }];
        const store = mockStore({}, expectedActions, done);
        store.dispatch(ASYNC_METHOD());
    })
    
    it("#METHOD()", () => {
        const expectedActions = [{
            type: ACTION_TYPE,
            ACTION_VALUES...
        }];
        expect(
            METHOD(ARGS)
        ).toEqual(expectedAction);
    })
})

]

.left-column[

What needs to be tested?

- Actions

- Reducers

] .right-column[

import expect from "expect";
import deepFreeze from "deep-freeze";

describe("Reducer::REDUCERS", () => {
    // default type
    it("#METHOD(undefined, {})", () => {
        expect(
            REDUCERS.METHOD(undefined, {})
        ).toEqual(EXPECTED_RESULT)
    })
    // action type
    it("#METHOD(DEFAULT_VALUE, {type: ACTION_TYPE})", () => {
        // declare default value and result
        const stateBefore = DEFAULT_VALUE;
        const action = {
            type: actions.ACTION_TYPE,
            ACTION_VALUES...
        };
        const stateAfter = EXPECTED_RESULT;
        
        // make data immutable
        deepFreeze(stateBefore);
        deepFreeze(action);
        
        expect(
            reducers.list(stateBefore, action)
        ).toEqual(
            stateAfter
        )
    })
})

]

.left-column[

What needs to be tested?

- Actions

- Reducers

- Components

] .right-column[

import expect from "expect";
import expectJSX from 'expect-jsx';
expect.extend(expectJSX);
import TestUtils from "react-addons-test-utils";

import React from "react";
import { Link } from "react-router";

import COMPONENT from "path/to/file";

const shallowRender = component => {
    const renderer = TestUtils.createRenderer();
    renderer.render(component);
    return renderer.getRenderOutput();
}
const shallowRenderWithProps = (props = {}) => {
    return shallowRender(<COMPONENT {...props} />);
}
describe("Components::COMPONENT", () => {
    let _props, _component;
    const setup = props => {
        _props = props;
        _component = shallowRenderWithProps(_props);
    }
    it("#render()", () => {
        setup({ PROPS_VALUE });
        expect(_component).toEqualJSX(
            REACT_ELEMENTS
        );
    })
})

]

.left-column[

What needs to be tested?

- Actions

- Reducers

- Components

- Containers.1

] .right-column[

import expect from "expect";
import TestUtils from "react-addons-test-utils";

import React from "react";

import CONNECTED_CONTAINER, { CONTAINER } from "path/to/file";

const renderWithProps = (props = {}) => {
    return TestUtils.renderIntoDocument(<CONTAINER {...props} />);
}

describe("Containers::CONTAINER", () => {
    let _rendered;
    const setup = props => {
        _rendered = renderWithProps(props);
    }
    
    // replace react component with mock component
    let COMPONENT;
    beforeEach(() => {
        COMPONENT = React.createClass({
            render() {
                return (<div>MOCK COMPONENT</div>)
            }
        });
        CONNECTED_CONTAINER.__Rewire__("COMPONENT", COMPONENT);
    })

__Rewire__: babel-plugin-rewire ]


.left-column[

What needs to be tested?

- Actions

- Reducers

- Components

- Containers.1

- Containers.2

] .right-column[

    it ("#render()", () => {
        // create spy for dispatch
        let fakeDispatch;
        setup ({
            PROPS_VALUES...,
            dispatch: fakeDispatch = expect.createSpy()
        });
        
        // check if container do call dispatch while componentDidMount
        expect(fakeDispatch).toHaveBeenCalled();
        
        // text node test
        let TEXTNODE = TestUtils.findRenderedDOMComponentWithTag(_rendered, "TAG_NAME");
        expect(TEXTNODE.textContent).toBe("Hello Redux Unit Test!");
        
        // react component test
        let RENDERED_COMPONENT = TestUtils.findRenderedComponentWithType(_rendered, COMPONENT);
        expect(RENDERED_COMPONENT.PROPS_VALUE).toBe(EXPECTED_RESULT);
    })
})

]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment