# Redux Testing ## Tests for Connected Components Connected components are wrapped in a call to `connect`. Way of solving the problem: - Wrap component with ``. Added benefit: new store dedicated to tests. - Add named export for unconnected component. In `Component.js`: ```js export function Component(props) { /* ... */ } // export unconnected component export default connect(mapStateToProps, mapDispatchToProps)(Component) // default export of connected component ``` In `Component.test.js`: ```js import React from "react"; // import enzyme or react testing library // import mock data import { Component } from "path/to/Component"; // import unconnected component // factory to setup test easily function testHelper(args) { const defaultProps = { /* default value for props in each test and required props */, history = {} // normally injected by react-router, could also import the router }; const props = { ...defaultProps, ...args }; return mount(); // or render if using react testing library } it("test description", () => { const dom = testHelper(); // simulate page iteration dom.find("selector").simulate(""); // find changed component // test expected behaviour of component }); ``` ## Tests for Action Creators ```js import * as actions from "path/to/actionCreators"; // import eventual action types constants // import mock data it("test description", () => { const data = /* mock data */ const expectedAction = { type: TYPE, /* ... */ }; const actualAction = actions.actionCreator(data); expect(actualAction).toEqual(expectedAction); }); ``` ## Tests for Reducers ```js import reducer from "path/to/reducer"; import * as actions from "path/to/actionCreators"; it("test description", () => { const initialState = /* state before the action */; const finalState = /* expected state after the action */ const data = /* data passed to the action creator */; const action = actions.actionCreator(data); const newState = reducer(initialState, action); expect(newState).toEqual(finalState); }); ``` ## Tests for the Store ```js import { createStore } from "redux"; import rootReducer from "path/to/rootReducer"; import initialState from "path/to/initialState"; import * as actions from "path/to/actionCreators"; it("test description", () => { const store = createStore(storeReducer, initialState); const expectedState = /* state after the update */ const data = /* action creator input */; const action = actions.actionCreator(data); store.dispatch(action); const state = store.getState(); expect(state).toEqual(expectedState); }); ``` ## Tests for Thunks Thunk testing requires the mocking of: - store (using `redux-mock-store`) - HTTP calls (using `fetch-mock`) ```js import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import configureMockStore from "redux-mock-store"; // needed for testing async thunks const middleware = [thunk]; // mock middlewares const mockStore = configureMockStore(middleware); // mock the store import * as actions from "path/to/actionCreators"; // import eventual action types constants // import mock data describe("Async Actions", () => { afterEach(() => { fetchMock.restore(); // init fetch mock for each test }); it("test description", () => { // mimic API call fetchMock.mock( "*", // capture any fetch call { body: /* body contents */, headers: { "content-type": "application/json" } } ); // expected action fired from the thunk const expectedActions = [ { type: TYPE, /* ... */ }, { type: TYPE, /* ... */ } ]; const store = mockStore({ data: value, ... }); // init mock store return store.dispatch(actions.actionCreator()) // act .then(() => { expect(store.getActions()).toEqual(expectedActions); // assert }); }); }); ```