Unit testable components pt.3: Testing components

My approach to component testing

There are multiple resources that show how to test components. When you read between the lines, they are actually describing their philosophy on how a component should be tested.

My tests are basic and practical because my components are dumb

My components have the following properties

  • No api calls
  • Small encapsulated components
  • Minimal Redux actions

As a consequence, my tests have the following principles

  • Shallow mount component directly. Ignore Redux's connect higher order function (HOF) in test
  • simulate interaction of ui elements and assert internal behavior of the event handler
  • Use setProps to change props (even redux store props), because I exclude the connect HOF
  • Indirectly test usage of setState by asserting value changes in this.state

Shallow mount component directly

A consequence of my practical approach to unit testing.

Mounting with HOFs greatly increases complexity and does not provide substantial benefits

Incorporating HOFs into your unit tests makes them more finicky and extremely verbose. From a technical perspective, including HOFs is more reflective of runtime behavior. But what exactly are you testing by doing so? What else is there aside from confirming that connect works? I don't see value in the huge complexity jump that HOFs add when it comes to unit testing.

Assert internal behavior of an event handler callback. Not the callback itself

You don't need spies if you do it this way

Having the event handler method run is not important. What is important is the internal behavior. If you simulate user input and it runs everything inside your handler method, doesn't that already imply the event handler was run?

Using jest.fn to mock functions is easy

Once you get into the testing groove, it is quick and concise to create mocks.

            
// component file: button's click handler
handleLogOut = () => {
    this.props.destroyLogOutUser()
}

// test file
const destroyLogOutUser = jest.fn()
const wrapper = shallow(<LogOut destroyLogOutUser={destroyLogOutUser}/>)

describe('component', () => {
    it('logs user out when clicked', () => {
        const menuItem = wrapper.find(MenuItem)
        menuItem.simulate('click')
        expect(destroyLogOutUser).toHaveBeenCalled()
    })
})
            
        

Use setProps for all props. Even Redux store props

Mocks behavior, but Redux store data is coming from an improper source

A consequence of directly testing the dumb component. I can see merit to anyone who takes issues with this concept. As said before, I do this because it is practical. Ultimately, it is still mocking the data that is necessary to test functionality.

Test consequences of the function call setState instead of spying it

You can only assert function calls for spies and mocks, which is a bad thing for setState

Keep setState intact and test how this.state changes after a component's behavior is run. You are indirectly testing to see if the call to setState has the expected behavior

                
// component click handler
openMenu = () => this.setState({ openMenu: true })

// test
it('opens the menu when clicked', () => {
    const dropdownButton = wrapper.find('button')
    dropdownButton.simulate('click')
    expect(wrapper.instance().state.openMenu).toEqual(true)
})
                
            

Next step is discussing how to test Redux actions. Something to discuss in part 4.

Comments

  1. Hey! This is my first visit to your blog! We are
    a collection of volunteers and starting
    a new project in a community in the same niche. Your blog provided us beneficial information to work on. You have done a marvellous job!

    React Native Development Company
    Reactjs Development Company Texas

    ReplyDelete

Post a Comment

Popular posts from this blog

Contrasting var and ES6s new declarations: let and const

Modularity vs. Abstraction. Which is More Important?