fenglin wang
Sep 18, 2018 · 5 min read

Testing code can be really beneficial to ensure future changes do not break codes that is already working. It gives you confidence in your code when you also write good tests. So in this article, I will be sharing 5 ways to write tests to test code segments that are hard to reach.


1. Fetching data

Fetching data from an API or testing a response code is very common, below is one way of testing it.

Segment of code we want to test:

  handleFetch = () => {
fetch(testEndpoint)
.then(response => response.json())
.then(body => {
if (body) {
this.setState({fetchResponse: body})
}
})
}

The component that we will mount in the test.

export class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
fetchResponse: {}
};
}
handleFetch = () => {
fetch(testEndpoint)
.then(response => response.json())
.then(body => {
if (body) {
this.setState({fetchResponse: body})
}
})
}
render() {
return (
<div>
Hit Button To Fetch
<Button onClick={this.handleFetch}>Fetch</Button>
</div>
)
}
}

First off in our test file, we use a helper function to mock the response.

const mockResponse = (status, statusText, response) => {
return new window.Response(response,
status: status,
statusText: statusText,
headers: {'Content-type': 'application/json'}
});
};

With this function, we are able to replace the window fetch and replace it with a jest mock function. Using Jest, window functions are mocked under global. When the test run, if our app calls fetch, it will access the newly defined global.fetch.

global.fetch = jest
.fn()
.mockImplementation(() =>
Promise
.resolve(
mockResponse(200, null, JSON.stringify(myResponse))
)
);

To test the actual fetch, we mount the component and “find” the button in our component and simulate a click. What it does is to trigger the function that is attached to the button.

  const wrapper = mount(<MyComponent />);
wrapper.find(Button).at(0).simulate('click');

Now, to add them all up in the test, it should look like this.

it('should update fetchResponse state with the response body when the API resolves', () => {
const myResponse = {
data: 'test'
}
global.fetch = jest
.fn()
.mockImplementation(() =>
Promise
.resolve(
mockResponse(200, null, JSON.stringify(myResponse))
)
);
const wrapper = mount(<MyComponent />);
//Perform some action in the component that does a fetch
wrapper.find(Button).at(0).simulate('click');

//assert that there is a state updated from the response
expect(wrapper.state('fetchResponse')).toEqual(myResponse);
});

With this function, we can modify the response code to test app behaviour under other response codes.


2. Finding nodes

As you see in the above example, we can find nodes and simulate actions on them.

Find nodes with component definition

//we import the Button component in the test
import Button from '../components/Button'
wrapper.find(Button).at(0).simulate('click');

Enzyme’s “find” function is very helpful as it will look for the node you specify. Below are a few other ways you can look for nodes and perform some tests.

Find nodes with CSS Selectors

wrapper.find('.myClass')
wrapper.find('input.myInputClass')
wrapper.find('#specialId')

Find nodes with display name

wrapper.find('Button')

Find with Property

wrapper.find({disabled: true})

The full “find” documentation is located here at enzyme docs.


3. Simulate events on nodes

After we find the node we can perform several actions to trigger certain functions. In the below example, we choose the first button and simulate an onClick on the button, this will trigger the function attached to the onClick prop.

wrapper.find(Button).at(0).simulate('click');
//Note that the .at(0) can be omitted if there is just one node

We can also simulate an onChange event. Say we want to simulate a user typing something in an input, we first create an event object and pass it in as the second argument in the simulate function.

const event = {target: {name: "email", value: "my_email"}}
wrapper.find('input').at(0).simulate('change', event)

What this does is to trigger the onChange event in input with our mock event object, we can eventually test by expecting the value of the input to have “my_email”.

So how to test custom function you might ask.

//we wont be able to use .simulate('slide') to trigger the function
<Slider onSlide={handleSlideFunction}>

Essentially, what .simulate(‘click’) does is to trigger the prop onClick on the node. So to trigger onSlide we can simply do this:

wrapper.find('Slider').prop('onSlide')();

If we want to pass arguments, we can add it to the function call.

wrapper.find('Slider').prop('onSlide')(myArguments);

The full simulate documentation is located here at enzyme docs.


4. Testing with redux

Most of the time, we would use redux in more complicated projects to extract the complexity of passing props from parent to child. However, testing redux connected components requires a bit of additional mocking.

First, to create our mock store we can add a package, redux-mock-store, and import the configureStore from it to our test file. You can also import your middleware, eg. ‘redux-thunk’.

import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

Now let’s mock our store values and actions.

const storeData = {
firstName: 'John',
lastName: 'Appleseed',
};
// we can import in the actions from our existing actions
const actions = [
setFirstName,
setLastName
]

To link everything up and we get our mock store

const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const store = mockStore(storeData, actions);

The readme on ‘redux-mock-store’ is located here.

We should also clear our actions before each test so as to assert the new actions being dispatched.

beforeEach(() => {
store.clearActions();
});

In our test, we can look for our input and call simulate. This will trigger the setFirstName action and assert that the store action is indeed the setFirstName action.

it('should mount and test setFirstName ', () => {
const wrapper = mount(<MyConnectedComponent store={store} />);
const event = {target: {name: "firstName", value: "Johnny"}}
wrapper.find('input').at(0).simulate('change',event);
const expectedActions = [
{ firstName: 'Johnny', type: 'SET_FIRST_NAME' }
];
expect(store.getActions()).toEqual(expectedActions);
});

Now to put it all together:

import MyConnectedComponent from '../'
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
setFirstName,
setLastName,
} from '../../actions'
const storeData = {
firstName: 'John',
lastName: 'Appleseed',
}
const actions = [
setFirstName,
setLastName
]
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const store = mockStore(storeData, actions);
describe('MyConnectedComponent Component', () => {
beforeEach(() => {
store.clearActions();
});
it('should mount and test setFirstName ', () => {
const wrapper = mount(<MyConnectedComponent store={store} />);
const event = {target: {name: "firstName", value: "Johnny"}}
wrapper.find('input').at(0).simulate('change',event);
const expectedActions = [
{ firstName: 'Johnny', type: 'SET_FIRST_NAME' }
];
expect(store.getActions()).toEqual(expectedActions);
});
}

To test our component without redux, we can simply export the React component and import it in our test file.

The connected component:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
action1,
action2,
action3,
} from '../actions';
export class MyComponent extends Component {
//render and functions...
}
const mapStateToProps = state => ({
//mapped state to props
});
const mapDispatchToProps = dispatch => {
return {
action1: () => dispatch(action1()),
action2: (input) => dispatch(action2(input)),
action3: () => dispatch(action3()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

In our test file, we can import the React component and test it directly, be sure to pass in the required props for the component to work!

import MyConnectedComponent , { MyComponent } from '../';

With this import we can test MyComponent like a normal component, without worrying about redux.


5. Testing functions in setTimeout

To test functions in timers we use jest.useFakeTimers(). This mocks the timers and allows us to control “time”, thus we do not need real time to elapse.

beforeEach(() => {
jest.useFakeTimers()
});

We can test our functions in timers by using jest.runAllTimers().

it('Should test setTimeout ', () => {
const mockFn = jest.fn()
const wrapper = mount(<MyComponent buttonFn={
setTimeout(mockFn,1000)
} />);
wrapper.find(Button)
.at(0)
.simulate('click')

//our button function is delayed by a setTimeout
//so it should not have been called yet
expect(mockFn).not.toBeCalled()

//run jest's fake timers
jest.runAllTimers()

//the mock function should be called
expect(mockFn).toBeCalled()
});

In this way, we can test our function in setTimeout. Jest have more functions to help test timer functions. The full documentation is here

2359media

Asia's leading mobile consultancy and the preferred choice for cross platform mobile engagement strategies

fenglin wang

Written by

2359media

2359media

Asia's leading mobile consultancy and the preferred choice for cross platform mobile engagement strategies

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade