bene : studio is a global consultancy, helping startups, enterprises and HealthTech companies to have better product
CI/CD workshop recap: React Native automated tests with Bitrise
At bene : studio, we love knowledge sharing to help the community of professionals. To share our 10+ years of experience we have launched a knowledge hub on our blog with regular updates of tutorials, best practices, and open source solutions.
Pardon the interruption, we have an important message!
We are looking to expand our team with talented developers. Check out our open positions and apply.
Introduction
This article is based on a live workshop we held in November 2021. Follow the steps in this article and watch the workshop video to complete the workshop at home!
Before you start
Prequisites
You’ll need three accounts and the Expo Go app for this workshop, all services have free options that should be more than enough:
Install Expo Go app on your phone from the app library of your choice: iOS, Android
Pro tip: Watch the workshop video to follow the steps in the article more easily
Adding the repo to Bitrise
First, fork the start repository on Github. You’ll be working with this code.
Next, open your Bitrise account and create a new Workspace if you haven’t already.
Now we can add a new app.
- Connect your GitHub account, as you’ll have to add SSH keys and setup webhooks
- Select the previously forked Github repository
- For the purposes of this workshop, auto-adding SSH key will be sufficient
- Set the branch to master
- The scanner will recognize your React Native code and prefill what it can
- For the iOS dev team ID and Android package name you can set any string, we won’t be using it during this WS
- You can skip adding an icon
- Make sure to register a webhook, this is required to be able to start Bitrise builds for new PRs and pushes.
Now we need to enable Github checks. Navigate to the app’s settings page.
- First, you need to install the Bitrise Checks app for your GitHub repository
- Then enable the toggle
Now it’s time to set up Bitrise workflows. Open the app’s Workflow editor.
Here you can see the default workflow for React Native that Bitrise automatically added for you. But since we already have a bitrise.yml in our repository we can use that.
Before we move forward let’s run through what a usual workflow looks like, in terms of the specific parts of the YAML file.
First the trigger_map. This part tells Bitrise, on which event what action needs to happen. For example, it’s a good practice to release your app from the master branch. So we specify that when new commit(s) are pushed to master (push_branch: master), we deploy our app (workflow: publish)
Workflows are independent pieces of logic, including several steps to do. There are built-in workflows (git-clone, cache-pull, activate-ssh-key) but we can define our own custom workflow as well. Take a look at the ci workflow. Before it’s run, a git-clone will happen to work on the latest code from git. Then next, cache is pulled from previous builds to reduce the time from Yarn install. Then custom scripts can be run, in our case `yarn test`.
Open the bitrise.yml settings and set to Stored in the app repository and hit Update.
We still have to add our Expo credentials. We can do that under the Secrets tab.
Add your login details with EXPO_USERNAME, EXPO_PASSWORD environment variables.
These variables are present during every workflow and can be used. In our deploy workflow these are used for logging in with Expo: `expo login -u $EXPO_USERNAME -p $EXPO_PASSWORD –non-interactive`
After this, we have to configure the Github workflow.
To do this, navigate to the repo and open a new pull request. The simplest way to do this is to edit the Readme. Make sure to check the new branch option and then create a new PR.
Creating a new PR should kick off a CI build on Bitrise. (Based on the configured trigger map)
It should finish successfully in about a minute.
Now we can enable branch protection rules on Github. With this, we can restrict merges to the master branch only if the CI ran successfully. Go to the branch protection rules, add a new rule for the master branch with these settings:
- Require a pull request before merging
- Require status checks to pass before merging
- Search & add the Bitrise check
Now we are able to merge our PR. This should trigger a publish workflow.
After the publish is done you should see the app on your phone in the Expo Go app (after you have logged in there too)
Implementing tests
Short introduction of the app
This repository contains a React Native application, using Expo and is written in TypeScript. The application itself is a quiz game. On the main screen, you can choose from a list of answers to a specific question. If the answer is correct, the next question is presented, and so on. In case of a wrong answer, your live count is decremented by 1, allowing you to choose a different (and hopefully a correct) answer.
When your life runs out, the game is over. If you made it to the last screen, you have won the game.
Write some unit tests
First, create a __tests__ folder and inside an App.test.tsx file.
Unit tests are written in a describe block and have separated it test cases. Let’s see a few examples.
The package we are using is the @testing-library/react-native. When we call the render function with the App component, we receive a lot of utility test functions, like findAllByTestId. It can be used to search for rendered elements in the DOM tree with the specified id. The number of elements is asserted to be the same as the maximum live count.
For the next one, first, create a mock response, this will be used instead of a network response.
The following section tests the interaction when the user is giving an incorrect answer. We know that in this case the number of lives decreases, so let’s check that.
fireEvent.press() is simulating a user interaction on that specified element.
Finish with snapshot tests
Snapshot tests are great for capturing the current state of the UI, this is called snapshot. With the help of this, we can test that if the UI looks exactly the same. First, create a Game.test.tsx in the same folder. The analogy is the same, we are writing describe and it blocks. This particular case will test the Game component. It needs an object as the input props:
jest.fn() is a mock function, meaning it does nothing.
Let’s see what happened here. The same render function is rendering the Game component with the above-specified props. toJSON is also a utility function, that gives a JSON representation of the rendered component. We can use this in the last line: toMatchSnapshot(). What it does basically, is to generate a snapshot and compares it to a previous one, which is written into Game.test.tsx.snap file. If just a single character is not matching to this snap file, the test failed.
To get a brief grasp of how all this comes together let’s jump back to our bitrise.yml file, specifically to the ci workflow. On every pull request open from branches to the master branch, this workflow will run. Besides caching, installing dependencies this workflow will run our tests. If there is at least one failing test, the pull request cannot be merged, since we configured it like so in GitHub.
Summary
During this workshop, we have added quite a few test cases and in the end, we even implemented a small feature with a test case to prove that the feature is working correctly.
The whole technical content of the workshop can be found in the final-solution branch.
Do you have questions?
Please send them to partner@benestudio.co, and we are happy to set up a talk with our engineers.
Are you looking for a partner for your next project? Check out our services page to see what we do, and let’s set up a free consultation.