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 theconnect
HOF -
Indirectly test usage of
setState
by asserting value changes inthis.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.
Hey! This is my first visit to your blog! We are
ReplyDeletea 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