The talk will be presented as a refactoring story - will start from the messy untestable component, cover it with a brittle smoke test, and then show how to move all our react component logic into a custom hook and test this hook. Will present patterns to test things like - useState, effects, and Apollo.
Testing React Hooks with Confidence
AI Generated Video Summary
The Talk discusses testing hooks in a React application. The speaker starts by showing an untested React application and begins testing with a simple placeholder test. The importance of refactoring and testing custom hooks is emphasized, with the recommendation to use the React hooks library and achieve full test coverage. Unit testing React components may not be valuable, as the complexity lies in the hook code. Instead, end-to-end testing with tools like Cypress is recommended.
1. Introduction to Testing Hooks
Hello, everybody. My name is Radoslav Stankov. Today we're going to talk about testing hooks with confidence. I'm going to show you this untested react application. We can start with just a simple small test. This is just a placeholder test to make sure when I refactoring, I don't break something.
Hello, everybody. My name is Radoslav Stankov. You can find me online over here. I'm head of engineering at Product Hunt, and currently I'm located in Bulgaria. If you want to check out my presentations, they're over here. All the slides and code are linked to this page so you can check them later.
Today we're going to talk about testing hooks with confidence. There is this big divide between test-driven development and automated testing as you might see during this presentation. In my talk, I'm just going to mention how to automate testing. I won't go to test-driven and stuff around that. Again, talk is cheap and I don't have much of it, so let's show you some code.
I'm going to show you this untested react application. This is just a small calculator app. This is the code of the app. If you notice, there is a lot of it. It's a pretty simple app, but there is a lot of code. How would we test? How do we know that it works? What we can start with is, we can start with just a simple small test. For the small test, we can just add those test data attributes, so we can select the elements, we can use the React testing library and we can create this very simple small test where we get the React testing library. Usually when I do tests, I like to have those helpers because overall I hate writing five functions with each other. So I just say, okay, click this data ID. And for my test, this is another small trick I do if I need to rename the component and I want to go back and rename the test. So here I just created a very simple small test. It just works. So I'm just testing, empty zeros, clicking plus one, one, plus two, remove, two, evaluate, and reset. Basically making sure that this component works. This is something that I will do normally to test if things work manually. This is just a placeholder test. This is not like a real good test. Like, I'm going to remove this in the end, but this just makes me sure when I refactoring, I don't break something. It's not a very good test because it doesn't follow the four phases of good testing.
2. Refactoring and Testing Custom Hooks
So where should we start now? The first thing we can do is clean up the code through refactoring. We can extract the big fat logic into a simple hook and test it using the React hooks library. After thorough testing, we can refactor the hook to use useReducer and achieve full test coverage. Unit testing React components may not be valuable, as the complexity lies in the hook code. Instead, end-to-end testing with tools like Cypress is recommended.
So where should we start now? So the first thing we can do is to clean up the code. What we can do is a refactoring. I like to call extract custom hooks. This is one of my favorite features of hooks is that you can actually boot hooks from other hooks and they create these very nice interfaces.
So here all these big fat logic can just be extracted into a simple hook, and this hook can give you, okay, that's what's the memory of the calculator and what are the actions. And again, the code for the hook is just copy paste, it works. So how do we going to test this custom hook? This is the next step we needed to do.
So for testing this custom hook, there is this nice library called React hooks from React testing library, which allows you to very easy test the hook and what I like to do is I want to have a function called emit hook, which basically hides the boilerplate setting up the hook. And for example, I want to test the add action. So for the add action, it's just setting up the hook, adding cut value and checking that after the hook is reloaded, we have updated our value and we can basically test, okay, if I have two digits, they're added, and you can go through all the tests for that, for remove is very similar. You set up the hook, you just call it, you have this nice call function where you encapsulate this and you have all of this. And again, it's a bit long and I would link you to the slides in the end so you can dig through more.
And now when we have this test, which very thoroughly test our hook with all its edge cases, with all its workarounds, we can actually start refactoring this hook. The thing we are going to end up in the end from this big ball of mud is basically refactor this hook to use just a simple useReducer where we say, okay, we have a reducer function and in the end we have the same actions add, remove, reset, evaluate. And this is again the reducer scope. It's a bit more complex. It's a bit more split, but this actually tests our calculator and we actually have like a full test coverage. And again, the code for this presentation is leaving this address so we can click it and go to the details. I actually split into steps so you can see all the refactoring steps. And a couple of the final notes I want to say here is in the end, I'm actually removing all that code from the smoke test. I don't need it. What I have found in practice is usually when I write React components, they're either very dumb, like they get some data and render stuff, so there's not much value adding to test them as a unit test. Or the biggest logic and the biggest thing I have in my test is the hook code. Like usually, that's the place where a lot of the complexity of my system is. So I generally stopped doing React unit tests. And what I try to do on the unit test level is to just unit test the simple function, like for example, I could just as the reducer, I can just test the hook by itself and not test the React components. The way I'm sure that the React components work is through doing end to end testing with something like Cypress or something like Capybara, which actually tests my whole workflow because I very rarely have found bugs in my system, which were regarded to just the React component itself. Usually the bugs come from very complex hooks or when we have class components, class components. So yeah, that's basically what I wanted to share with you today.