Tag Archive

Tag Archives for " unit testing "

Test Clicks On Connected Components In Under 10 Lines of Code

Here’s a fairly benign component, that can give developers pause when its time to write unit tests for your components: 

const ClickableButton = props => (
  <button onClick={props.doSomething}>Click Me!</button>
)

const mapDispatchToProps () => ({
  doSomething: dispatch(someFancyAction())
})

export default connect(
  null, 
  mapDispatchToProps,
)(ClickableButton)

There is only thing worth testing here: That when we click the button, the function we passed in as a prop gets called. That prop function could be a complex chain of actions & API calls. You don’t want to stress about that, at least not in these tests.  But to do this, are we going to have to create mock store and a provider component just because it’s connected? Ugh!

There has to be a better way. 

You Can Remove The Redux Connection Entirely

While you want your component to be the default export, you can export the unconnected component, and just test that! Hat tip to Dave Ceddia for show me a better way to export a connected component.  All you have to do is change one line: 

export const ClickableButton = props => (

As an additional trick, if you have any helper functions not attached to the component, you can export those as well for easy testing. 

With Jest and Enzyme, The Rest is Easy

Inside our test, import the unconnected component. Then, you can create a mock function using Jest, and simulate the click using Enzyme. Here’s what the test looks like all together: 

describe('<ClickableButton />', () => {
  it('calls the doThing prop when the button is clicked', () => {
    const props = { doSomething: jest.fn() }
    const wrapper = shallow(<ClickableButton {...props} />)
    wrapper.find("button").first().simulate("click")
    expect(props.doSomething).toHaveBeenCalled()
  })
})

How to Test Async Redux Thunks

In a previous post, you learned how to make HTTP requests inside your redux application. We use redux-thunk, a library that enables you to dispatch functions in addition to flux-style actions. With it, you can dispatch functions to handle more complex cases such as asynchronous operations. But then how do you test them? Testing can be more work than writing the functioning code itself. Dealing with server responses and timing was hard enough as it is. Luckily, there are tools and patterns you can apply to work, and make your codebase more reliable with ease.

First, we’ll take a look at the tools that we will be using for testing. Then, how to apply them to our operation. 

Tools of the Testing Trade

  • Jest – Jest is a JavaScript testing library from the Facebook development ecosystem, just like React. It’s designed to require no configuration and get out of your way you write tests easier and faster. 
  • Redux-mock-store – Since the primary goal of the action is to update a redux store, you will need a way to mock the redux store. redux-mock-store does just that.
  • Moxios – Moxios is a Javascript library that stubs out Axios requests. We’ll use this to decouple our code from the server so we can isolate our logic and test only

Quick Review: Our HTTP Action

Here’s the thunk from the previous tutorial: 

const fetchPosts = () => {
  const url = '/our-app/posts.json'
  return (dispatch) => {
    dispatch(actions.loadPostsRequest())
    axios.get(url)
      .then((response) => {
        dispatch(actions.loadPostsSuccess(response.data))
      })
      .catch((error) => {
        dispatch(actions.loadTaskHistoryFailure(error))
      })
  }
}

Now, let’s get to work on adding some tests to this code, so that we can make changes to code base without fear of causing a regression.

1. Create a Mock Store

First, we’ll set up our store. Since our redux application uses the thunk middleware, we’ll also need to apply that middleware when testing. Once we’ve done that we’ll create a function to help us set up our state for tests. Many applications have some kind of an initial state. Instead of creating that for every test, instead, we’ll create a helper function that takes a configured store, and combines the initial state with the state you pass as an argument.

import thunk from 'redux-thunk'
import configureMockStore from 'redux-mock-store'

export const startState = {} //put initial state here

export const mockStore = configureMockStore([thunk])

export const makeMockStore = (state = {}) => { 
  return mockStore({
    ...startState,
    ...state,
  })
}

2. Create Some Moxios Helpers

Next, let’s create a couple of helper functions for moxios. Axios & Moxios look at the status code to determine whether or not to resolve or reject the promise. These two functions will save us a bit of time when writing multiple API tests. These helper functions will save you quite a few keystrokes if your application has a lot of different HTTP-based thunks.

const mockSuccess = data => ({ status: 200, response: { data } })
const mockError = error => ({ status: 500, response: error })

3. Configure Setup and Teardown for Your Tests

For our tests, we’ll need to set up and tear down the Moxios adapter. It intercepts outgoing HTTP requests, allowing you to control the response the function you are testing gets back.  Here’s what that looks like: 

describe('fetchPosts', () => {
  beforeEach(() => moxios.install())
  afterEach(() => moxios.uninstall())
})

4. Write Your On Success Test

What do we want to assert here?

You aren’t testing any of the server-side logic. You aren’t testing that state changed because that’s the reducer’s job. You should write separate reducer tests for that. The thunk is only responsible for deciding which actions to dispatch, so that’s what to focus on.

So the jobs our test needs to accomplish are:

  1. create a mock instance of the store. You’ll be dispatching actions to it. 
  2. create a mock server response. 
  3. call your thunk, and assert that it dispatched the correct actions to your mock store.

Altogether, it looks like this. 

  it('dispatches loadPostsSuccess with server data on success', () => {
    const response = ['some', 'posts']
    const store = makeMockStore()
    moxios.wait(() => {
      const request = moxios.requests.mostRecent()
      request.respondWith(mockSuccess(response))
    })

    const expected = [
      actions.loadPostsRequest(),
      actions.loadPostsSuccess(response),
    ]

    store.dispatch(fetchPosts()).then(() => {
      const actual = store.getActions()
      expect(actual).toEqual(expected)
    })
  })

5. Now do the Same for the Error Response

Don’t just test the happy path. When writing tests it’s prudent to ask yourself, “what could go wrong?” Our server could throw an error response, so we want to test for that use case as well. In our example, the error test case looks almost identical to our success test case. 

  it('dispatches loadPostsError with server data on success', () => {
    const response = 'error message'
    const store = makeMockStore()
    moxios.wait(() => {
      const request = moxios.requests.mostRecent()
      request.respondWith(mockError(response))
    })

    const expected = [
      actions.loadPostsRequest(),
      actions.loadPostsError(response),
    ]

    store.dispatch(fetchPosts()).then(() => {
      const actual = store.getActions()
      expect(actual).toEqual(expected)
    })
  })

Apply This To Your Applications

This is the purest example of how to test asynchronous operations in your application. Of course in the real world, it’s never quite that simple. If there are additional use cases that you can think of, be sure to write tests for those as well. For example, are there different kinds of successful or error responses you could expect from the server? Do you need additional logic to handle them? If so, it could be a use case for creating additional tests. 

Think through your different use cases and decide the best approach.