Software testing is all about gaining confidence: When we're doing changes in our application, we don't want things to break.
So, what can we do to prevent production failures?
Well, we run our app and go throw the critical paths trying to find any new bug introduced by our changes. This could be really tedious work, but here is where automated testing could save us some time.
Today we are focusing on unit testing. We'll be testing separate chunks of code and we’ll do it keeping our test as close to the way a final user would use our app as possible.
- Don't focus on the implementation details of what you're testing.
- Try to keep the automated testing as close as possible to the way the user will interact with our components.
- Think how you would test your app if you were a manual tester.
To get this we are going to use React testing library (it comes ready to go with your create-react-app)
React testing library provides us a `render` function that allows us to render a component. It’s super straightforward, you just need to call the `render` function with the component you’d like to test, and that’s it.
We are also importing `screen`, which helps us to validate that the UI looks the way it should.
It's important to render the component before starting to use screen methods. With screen.debug we get the React Node of the element printed on our terminal, so we can see what’s being rendered by our test. This is super useful and time-saving.
With our component rendered, we are ready to start our UI-based validations. Our sample app has a counter, and it must start at 0 when it’s rendered, so let’s make sure this is happening.
You can learn more about screen getters here:
In this scenario we are using screen.getByText, which takes a string or regex to get the element we want.
`expect` is the way we assert the results of a test, it comes from the Jest testing framework (https://jestjs.io).
`toHaveTextContent` is part of jest-dom matchers, learn more:
With those three lines, we just validated the initial state of our counter.
Now, let’s make sure it’s value increases and decreases when increase and decrease buttons are clicked.
Here is where things get interesting: We’ll be using userEvent to simulate user interactions, such as clicks and keyboard strokes. We’ll be doing a basic usage of this but you can dive into its docs to check the full capabilities of userEvent:
Let’s test the increment and decrement buttons.
The first thing we need to do is to use screen functions to get the `count` element, then we’ll need the `increment` and `decrement` buttons.
In this scenario we are using `screen.getByRole` (full description here), which takes the role of the element as first parameter and also takes an object as second parameter. We can use the name property of that object to query the elements of the given role.
Once we get the element, we’re using the `userEvent.click` function to click our elements, `userEvent.click` takes the element we’d like to click as its first parameter.
In the above test, we tested the functionality of the counter, without minding how the counter is implemented- we don’t care, the same way the final user won’t. This way we get a future-proof test decoupled from the implementation details.
Next, we're using userEvent to fill some inputs on our Form component, but first, we’ll need to give a simple intro into Jest mock functions.
Basically, it is a function that allows us to expect a bunch of different things like the parameters the function was called with and the number of times the function was called.
Read more on the Jest docs. The syntax to declare a Jest mock function is:
First we define our test data (`randomUser`), then we define a submit handler Jest mock function, and we pass it to Form’s props (just as we do when using the Form component). For this test we’re using `screen.getByLabel` to get our inputs, and `screen.getByRole` to get the send button.
To fill our inputs, we have userEvent.type, which takes as its first parameter the element we’d like to type on and the string we’d like to type as the second one.
To sum up, we just filled the inputs, we hit the send button and we checked that the handleSubmit function was called with the right data. We also checked that the function was called once.
Sometimes our components need a wrapper that provides its context. Gracefully, `render` function takes an options object as its second parameter and as you may be guessing: Yes, you can pass a wrapper as a render option.
First, let’s create a `test-utils.js` file:
We just created our `reduxRender` function, and it’s ready to be used. But first, let’s do the same for a React ContextAPI example:
Just to show that the tests are actually the same, no matter if it's the Redux one or the ContextAPI one, we create our validateCounter function. We just need to call the respective render for each component, and call `validateCounter` with its initial value:
Here is where MSW joins the party. Sometimes we need to test how our components integrate with external APIS. Luckily we have MSW; a full set of API mocking tools that intersects requests on the network level. We'll be covering just a few of them, specifically `rest` and `setupServer`. You can have a look on MSW: https://mswjs.io/
First we need to set up our mocking server:
Alright, we are done with the server setup, now let’s write some tests.
Thanks for reading!
That’s all for now: we covered some simple scenarios, the main idea of this blog is to show how simple it could be to start doing some automated testing with this powerful tool. I strongly recommend going through the docs to have a look at the full capabilities that React Testing Library provides.
Last but not least, here is a link to my sample repo for the tests we did. Please feel free to submit an issue on anything you think could be improved (or maybe a PR if you’d like to) https://github.com/facundop3/testing-react-workshop
Ready to get started? Use the form below or give us a call to meet our team and discuss your project and business goals.
We can’t wait to meet you!