In the previous post we outlined the first few techniques for making components testable.
When you feel that testing is harder than it should be, see if applying these techniques helps make your tests simpler and clearer.
When Arranging a component test, the easiest approach is to use Props to set up the component. Anything that you can’t control via a Prop is harder to test.
In the example above, the component doesn’t take any Props, but it does have a bunch of things it relies on that will matter for testing:
There’s a library that needs to be mocked as its used in the component.
The theming library has some internal state that needs to be set up before rendering the component.
And the component uses the current time of day.
Having these elements makes testing harder. But, you can convert all of these to be Props instead:
Now, the Example component has more Props, and of course these need to be passed in whenever its rendered (be that in a test or wherever it’s used), but we have the lovely benefit that it is very clear what the component is depending on. This makes testing very direct — we can even fake the time of day the component uses, making asserting it simple.
This is an example of how recognising complexity, and then moving complexity around, helps you write better components. Components with really clear and explicit Props are easy to test and easy to re-use. And whilst there’s a delicate balance in play (by moving complexity around, you should check if you’re making things better or worse), this principle of making components more straightforward, and their dependencies explicit, will come into the remaining techniques.
You could view this as related to pure functions, and indeed React has its own PureComponent API.
It’s worth stating the obvious sometimes. A component that takes a stringValue and returns <h1>{stringValue}</h1> is going to be pretty easy to test. And a component that takes a mix of props, loads something upon mounting, provides some ways of being interacted with, updates its own state, uses context and follows some complex business logic, is going to be harder to test. A good way to make a component easier to test is to try and write “dumber” components. Keep your component looking more like the first case than the latter, and it’ll naturally be easier to work with.
The following are signs that you have a clever (i.e. more complicated) component:
State — see the following tip.
Business Logic and complex lifecycle/effects — better kept in one place.
Using native or “test-unfriendly” libraries — wrap them in a Prop instead.
Context — often a Prop is easier to use.
If you’re finding it hard to test a component, see if it has these qualities and if they can be removed. Typically, removal will mean moving things to a parent component instead. This isn’t something I can recommend 100% of the time — you need to judge whether making your component dumber is at the expense of over-complication elsewhere.
Again, this is an example of a technique where some judgement is needed. But I would generally contend that controlled components, that do not hold State, are easier to test than uncontrolled components. A good example of the difference between the two can be found here.
Unless the change in State in an uncontrolled component changes the output in some easy to Assert way (i.e. a label changes or a new element is rendered), it’s hard to check that State has changed in the way you’d expect. With controlled components, you typically have some kind of Prop named something like onValueChange, a function that the component is given to call to tell its parents that they should change their state. This Prop can easily be a mock function (like from jest.fn() ), and its straightforward to Assert if the function has been called and with which arguments:
Something I find interesting — and it’s probably just human nature — but when a developer is writing a React app (for instance), oftentimes the business logic associated with the app is found inside components. It’s almost like we switch to “React-mode”, start writing components, and this has a mysterious magnetic pull — and then we get into an approach where everything we add fits into components too. I’ve seen similar with other frameworks too.
But looked at rationally, of course there’s no need for this! We’re under no obligation to have every piece of code we write in a React app live inside a component somewhere. We should remember we’re just writing Javascript (or Typescript or whatever) — if we want to write a Plain Old Function for something, we can. It doesn’t need to live in render or componentDidMount or a useWhatever hook.
What’s great about remembering this, is that plain functions are easier to test than React components. If you have a function that takes complex arguments, and you want to test lots of interesting combinations of the arguments, you can — and despite the best efforts at streamlining React component testing, it’ll be more concise and direct too. So, if you’re finding a component hard to test fully because it holds a lot of complex business logic, try moving the logic out into a separate function instead.
There’s a further step you can take after the above technique:
First of all, you have a component with complex business logic mixed into it. Then, you extract the business logic out into a function that wraps it all up, and your component can import and use this. But you can also combine our previous techniques, and rather than importing the business logic function (calculateTotal in the example above), you can inject it instead.
There’s lots to read about Dependency Injection, but one form of it (Constructor Injection) is similar enough to how React and Props work. By making the function a Prop rather than importing it directly, you can do a few things like:
Mock the function and check how it’s called and with which arguments, but don’t actually call it.
If the function is complicated to run, ensure this doesn’t complicate the tests for components that use it.
You can also go further, and if the function has a complicated signature then you can pass a simplified function — perhaps with no arguments, or fewer arguments — to the component by using something like Lodash’s partial function, or just a plain arrow function, to “wrap up” the function call.
Over the last two posts I’ve outlined a few starting points for ways of changing your React code when you encounter difficulty testing. I’ll apply the usual caveats around these not always being a good fit, and judgement being required on a case by case basis, but the important lesson here is that when having difficulty testing, its generally the thing you’re testing making it difficult, not the testing itself.
What rules of thumb do you have for writing good, testable, code?
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.