Test Utils

Let’s start by breaking down /test-utils

makeMountRender — a util function that accepts a component and some default props. We return a function so that we can override the default props if desired. This function returns a react wrapper using enzyme’s mount. Because we are interested in integration testing, we are interested in full DOM rendering which is closer to how the actual user will use the application. I opt to never use shallow rendering.

import { mount } from 'enzyme';

export const makeMountRender = (Component, defaultProps = {}) => { return (customProps = {}) => { const props = { ...defaultProps, ...customProps

}; return mount(<Component {...props} />); }; };

makeStore — a util function that is used to create a new redux store. We import the rootReducer and the createStoreWithMiddleWare function from our actual application which makes it easy to create a new store for testing purposes.

import { mergeDeepRight } from 'ramda'; import rootReducer from '../reducer'; import { createStoreWithMiddleWare } from '../store';

export const makeStore = (customState = {}) => { const root = rootReducer({}, { type: '@@INIT' }); const state = mergeDeepRight(root, customState); return createStoreWithMiddleWare(rootReducer, state); };

reduxify — a util function takes a component, props and an initial redux state and returns a new react component which renders the passed in component in a redux environment.

import { ConnectedRouter } from 'connected-react-router'; import React from 'react'; import { Provider } from 'react-redux'; import history from '../history';

export const reduxify = (Component, props = {}, state = {}) => { return function reduxWrap() { return ( <Provider store={makeStore(state)}> <ConnectedRouter history={history}> <Component {...props} /> </ConnectedRouter> </Provider> ); } };

snapshotify — a util function that accepts an enzyme react wrapper and calls its html method returning an HTML string.

export const snapshotify = reactWrapper => { return reactWrapper.html(); };

Snapshot tests should not test implementation details. This util function isn’t doing much, but it abstracts the html call and reminds us that snapshot tests should be returning HTML.

If we use jest-serializer-html then we end up with beautiful snapshots:

exports[`<ChooseYourPokemon /> matches snapshot 1`] = `

<div>

<p>

Pokem ipsum dolor sit amet Mime Jr Mime Jr Watchog Nidoran Professor Oak Pichu. Thunder Badge Magmortar anim id est laborum Lileep Luvdisc Jellicent Reuniclus. Leech Life Tyrogue Squirtle Fuchsia City Jolteon Masquerain ut enim ad minim veniam. Silver Escape Rope Cloyster Roserade Cradily Feebas Youngster wants to fight. Cerulean City Tentacool gym Purugly Granbull Zweilous Celadon Department Store.

</p>

<button data-test-id="choose-pokemon">

Choose!

</button>

</div>

`;

Here is an example of a bad snapshot:

exports[`<ChooseYourPokemon /> matches snapshot 1`] = `

<Provider

store={

Object {

"dispatch": [Function],

"getState": [Function],

"replaceReducer": [Function],

"subscribe": [Function],

Symbol(observable): [Function],

}

}

>

<Connect(ConnectedRouterWithContext)

history={

Object {

"action": "POP",

"block": [Function],

"createHref": [Function],

"go": [Function],

"goBack": [Function],

"goForward": [Function],

"length": 1,

"listen": [Function],

"location": Object {

"hash": "",

"pathname": "/",

"search": "",

"state": undefined,

},

"push": [Function],

"replace": [Function],

}

}

>

<Connect(ChooseYourPokemon) />

</Connect(ConnectedRouterWithContext)>

</Provider>

`;

This is an extreme case and a really bad snapshot but it’s a good example.

The actual snapshot test looks like this:

import { makeMountRender, reduxify, snapshotify } from '../../test-utils'; import ChooseYourPokemon from './';

it('matches snapshot', function() { const wrapper = makeMountRender(reduxify(ChooseYourPokemon))(); expect(snapshotify(wrapper)).toMatchSnapshot(); });

We pass our component into reduxify and then pass that into makeMountRender, calling it. The result is an enzyme react wrapper that can be passed to snapshotify.

All I did to produce that second snapshot was change makeMountRender to use enzyme’s shallow and change snapshotify to just return the passed in react wrapper.

If your snapshot includes props and component names, your test will break when you change or refactor them even though you may have correctly updated them everywhere. You are testing implementation details. The actual rendered HTML is what we are interested in. That way, you can refactor your component tree to your heart’s desire and your tests will still pass.

mocker — a util function that accepts an api mock and returns an object with methods to mock API calls. I am using axios and axios-mock-adapter but there are many others.

export const mockData = { pokemon: { id: 26, species: { name: 'raichu' }, sprites: { front_default: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/26.png' } } };

export const mocker = apiMock => ({ fetchRandomPokemon() { apiMock .onGet(/https:\/\/pokeapi.co\/api\/v2\/pokemon\/\d+/) .reply(config => { const numberCheck = RegExp(/pokemon\/(\d+)/); const pokemonInt = Number(numberCheck.exec(config.url)[1]); if (pokemonInt > 151) { return [500]; } return [200, mockData.pokemon]; });

return this; } });

This is our only mock.

ticks — a util function that accepts an array of callbacks to be executed asynchronously. This way, we can trigger asynchronous code in our test and then push a callback to the queue to be called when our test code has finished running.