How to Directly Test React Component Methods with Enzyme
TIL how to directly test a React component method using enzyme wrapper.instance()
STOP! Read This First
After almost 2 years since publishing, I’ve come to realize that directly testing react component methods is a bit of an antipattern… It is a form of white box testing that can lead to brittle test and components that become difficult to change.
See the following post for an updated view on the subject, as well as a discussion of the differences between white box and black box testing.
The Original Post
See here for a repo with a working version of the sample code covered in this post: https://github.com/bambielli/testing-react-component-methods
A common pattern when testing React component methods using the AirBnB enzyme library, is to figure out what event triggers the method through normal usage of the component and simulate
that event to indirectly trigger it.
While this is a valuable test to ensure your component behaves correctly in response to events, it can become tedious and difficult to configure a component in just the right way to fully exercise a complex method indirectly.
I recently learned about the enzyme wrapper.instance()
method, which returns the component instance inside of the wrapper. Getting access to this instance allows you to directly invoke component methods, instead of resorting to event simulation to indirectly trigger them.
Access to the instance also allows you to spy on component methods using jest.spyOn()
, which can be useful to ensure that complex interactions between helper methods occur as expected.
Example
Here is a home
component, which contains a button and a piece of counter state.
I’ll show a few different ways to test the incrementCounter
method of the component above, both indirectly and directly.
Testing incrementCounter Indirectly
An indirect way of testing the incrementCounter
method would be to simulate a click event on the button to which the method is attached using the enzyme wrapper event simulation functionality.
When indirectly testing a component method you have little control over how the method is invoked. This often requires configuring state + props in different ways before indirectly triggering the method to make sure all branches were fully exercised. In the tests above, I set up two different versions of the component (one with and one without the two
prop) so I could hit both branches of the incrementCounter
method.
Testing incrementCounter Directly
Now let’s see what it looks like to test the incrementCounter
method directly, using the component instance returned by wrapper.instance()
.
Clearly there is a lot more control here: invoking an instance method directly allows you to call the method at any time. This is particularly convenient when methods are pure functions (i.e. they do not depend on any external state to behave correctly) since they can be fully exercised by invoking them with different parameters.
If the incrementCounter
method did not accept the argument two
, and instead relied on it being available on props, I would still need to configure my wrapper to make sure props are correctly set before directly invoking. Just another argument for keeping functions pure for ease of testing.
Spying on incrementCounter
Finally, it is also possible to use jest.spyOn()
to spy on component methods through the component instance returned from wrapper.instance()
.
This is yet another useful way of testing to make sure only the component methods that you expect to be called are called in response to a simulated event from the component. Spies also allow you to confirm that methods were called with the correct arguments.
If you are used to jasmine
or sinon
spies, you might expect jest.spyOn()
to automatically replace the component method with mock implementation. This is not the default behavior of jest.spyOn()
. If you want to provide a new implementation to a function you are spying on in this manner, try the following:
Learning about wrapper.instance()
definitely allowed me to get more granular with my testing, and allowed me to simplify some of my test setup for more complicated scenarios that I was previously testing indirectly through event simulation.
Comments