Purpose of the Article: To spread awareness of doing Unit testing properly in React Native, for which very little material is available on the internet.
Intended Audience: Mobile Developers using React Native Framework
Tools and Technology: VS Code, React Native, TypeScript, Jest
Keywords: Unit Testing, React Native, Testing Library, Jest, Mocking Libraries
I was asked to write Unit test cases in one of my ongoing client projects recently, so I started my research. I found out that there is a lack of good material to understand concepts properly. Information is scattered, and it is difficult to understand how best to get started with Unit testing in React Native.
After a long search on the internet, I came across this quote from the Bollywood movie – ‘A Wednesday’:
So, I was forced to try on my own and learn from my mistakes. Today, I am trying to make the world a better place by sharing whatever I have learned from my experience while working on Unit test cases for React Native.
What is Unit testing?
Unit tests are automated tests created and performed by software engineers to check if a portion of an application conforms to its design and acts as expected.
What are the advantages of Unit testing?
- It helps in making development easy by running tests automatically on CI/CD pipeline.
- Quality of code can be improved as the environment can be set in a way where a certain percentage of coverage is crucial for even committing the code locally.
- Finds some non-functional bugs very easily. E.g., an error message should come if nothing is written in text input and ‘submit’ is pressed on the login screen.
- Writing test cases drives the developer to consider scenarios in which the code could fail, and this practice often improves the code to the point where very few defects are discovered after QA.
- It saves time for the QA team by checking some basic bugs automatically; hence QA team can focus more on functional bugs and reduce the overall project cost.
Are there any disadvantages of Unit testing?
Yes, there are disadvantages of Unit testing, and software developers need to know those while writing Unit test cases. They are as follows:
- Usually, developers must write more code along with the feature; they also must write test cases. It is a norm to have at least three test cases for one feature as standard practice. However, this increases time. This increased time of completing a feature implicates more costs.
- Unit tests usually are a headache when it comes to testing UI.
- Unit tests can’t find integration errors. So, one can’t depend on them to cover all errors inside an application.
- There must be basic sense while writing a Unit test. It should be part of the overall functionalities.
Which library to go for?
Okay, now that we have understood what a unit test is and what advantages and disadvantages does it have, let us understand what the available library options are to implement unit tests in our projects.
React Native comes prepacked with jest, by default when you create any project and run the command.
You’ll see one test succeeded in the output. This is because the boilerplate code for the project is produced by default with a “__test__” folder and an “App-test.tsx” test file.
Apart from jest, there are other libraries as well like enzyme, mocha, chai, etc.
There are different setup steps required for all libraries other than jest. All libraries have one or the other advantage over each other, but that’s a separate point of discussion. We are going to use jest in this tutorial.
Using these libraries, one can write production-level test cases. These libraries will help you get the coverage of your projects. However, you can’t write automated test cases with them. For writing automated tests which don’t require manual clicks to mock a specified flow, you need detox, jasmine, karma, (____s/w or libraries) etc.
There is one more kind of library that we will use and focus on mainly in this article which is React-Native-testing-library. This library is nothing but a wrapper on jest library and helps provide some useful functions prepacked for us, which reduces our efforts.
Okay, enough of talking, Let’s get started with writing some tests now.
Creating Project & Installing Library
First, create a project with the following command.
React-native init UnitTestingSample
Once the project gets created, open the terminal on the project root directory and hit the following command.
yarn add @testing-library/react-native
Writing first test case.
Now we will render the App.js file and see if the test passes or not. We can do that using the following code.
In the above screen, we use the render function from react-native-testing-library and check if the wrapper has a truthy value. If the page doesn’t get rendered properly, then this test case will fail.
The next line has to MatchSnapshot(), helps in creating and storing widget tree. This is done to check the widget trees with every subsequent test. Whenever the desired widget tree is stored, all the tests that you run will be checked with the stored output. It is helpful to see if the page is rendering something useful or not.
Describe function is used to write test cases of similar type; for e.g., All login page test cases, will only be written in the describe function. This is referred to as Test Suit. One test suit has multiple test functions.
Let’s test if the test passes or not; hit the following command.
You will see that the tests are passing, and you are getting the following output.
You can see the newly created snapshots in the __test__/__snapshots__ folder. That’s great, we have written our first test. However, real-life pages are too complex with redux integrated, custom components, and third-party libraries. So, let’s have a real-life example and write a test case for it so that one can write tests for real-life projects as well.
Creating tests for a real-world example
To make you understand how to write test cases for a real-world page, I will use a sample project. The GitHub link of the same is below.
The project has a login page and uses redux to make an API call to a free API provided by https://reqres.in/#support-heading.
Let’s start and check if we can write a test case for this file.
First, let’s create a separate test file for creating test cases for the login page as login-test.tsx and paste the following test case in it to check if we write snapshot test for the existing login screen will pass or not.
Hit yarn test login-test in the terminal and see the output. The test is failing, and the reason for that is that our login page is not wrapped with the provider. It is important to understand that the unit testing environment is different from when we run the app. The execution starts with the index file where the provider is already wrapped when we run the app. This is not the case when we write the test cases and hence the error.
So, let’s wrap the provider around the login file and check if the tests are passing.
Let’s test the unit test case again and see if it’s passing.
The test case is passing, and the snapshot also has been created.
I will add a separate section as to what snapshot testing is and how much useful it is.
So now, let’s start writing test cases for the login screen that we have designed. This will include UI tests along with functional tests.
We have an error message that triggers its visibility when we pass incorrect credentials to the API. Let’s write the test case to check if we pass incorrect credentials, the error message is displayed or not, and if it is displayed, is it displayed with a proper message or not.
To test the above, we need to pass some values to the TextInput, and to do so, we need to add testID to all the test input and login buttons.
Now that we have added the testIDs let’s head back to the login-test.tsx file and write a test case for the error message.
To add text in the Text Input, we need to use the fireEvent method from react-native-testing-library. fireEvent method gives us the changeText method using which we can add any text in the TextInput as shown below.
We will add text in the password text input similarly.
Now we have added the wrong credentials in the text inputs. Now let’s see how we can tap on the login button.
We can use the fireEvent method again for this. However, this time we will use the press method from fireEvent.
This method will press the login button. Now we need to check if the error message’s visibility is toggling or not. For this, we will use the waitFor method from the react-native-testing-library.
Here’s the final test file before we check the test case.
Now when we check the test case, we can see that the test is giving some warning as below:
Warning: You called act(async () => …) without await. This could lead to unexpected testing behavior, interleaving multiple act calls and mixing their scopes. You should – await act (async () => …).
This is because the login button is hitting an API, and it is an asynchronous task. We must wait for it to get our desired result. For this, we need to use another function called ‘act’ from the testing library.
We modify our login click as follows:
If we check the result, our test is still failing. The reason for this is that the new state has not been updated yet. Whenever you have state changes and you’re writing a test case dependent on a state change, you have to re-render the screen before you get the updated states. To do the same, we use re-render () from wrapper as follows:
If the above error gives you a warning, then make sure to wrap this command in the act method.
The final test case is as follows:
Ta-da! our test case is passing, and this is how you write a test case for a production level page having API included.
There is one more aspect of the unit testing for real-life scenarios. It is called ‘Mocking.’ When you have third-party libraries installed like Async Storage, Google Analytics, etc., you might have to mock those libraries for passing your tests.
Mocking Libraries in Jest
Let’s understand the mocking with an example. Say we have added Google Analytics in our project and are trying to write a test case, it will give an error as logEvent is not defined. The reason behind this happening is the fact that the test environment is not the same as the environment where we run the app. Hence test environment will not go and resolve the dependency, and it will ask you to do it. So, to resolve logEvent and other analytics functions errors, we will mock the analytics library as follows:
This mock will replace all the listed functions inside analytics with a mere jest.fn(), which is nothing but a normal function. Hence you will not get logEvent error anymore.
Similarly, you can mock any function or promise from any library in jest. You can even return your custom values. e.g., you can mock react-native and return ‘ios’ or ‘android’ values for the Platform function.
I am going to list down some of the mocks that I have widely used in my project.
Async Storage Mock:
Keyboard Aware Scroll View:
Reach out to me for any help required for mocking libraries.
Check out Part 2 of the blog that covers coverage in detail.
I will cover redux test cases in the next part. Stay Tuned!