Blogs

calendar img
vijesh_profile
Vijesh ChoudhariSoftware Developerauthor linkedin

Beginners Guide to Setting up a CI/CD Pipeline for React Native

img

Introduction

CI/CD, or Continuous Integration and Continuous Delivery, is a pivotal set of practices ingrained in modern software development workflows. The fundamental objective is to streamline and automate the entire software delivery pipeline, from source code integration to production deployment.


Continuous Integration (CI)

As seasoned developers, we comprehend the significance of seamlessly integrating code changes. CI involves the frequent and automatic integration of code modifications from diverse contributors into a shared version control repository. This triggers automated builds and tests, offering swift feedback on the integration status. The aim is to promptly identify and rectify integration issues, ensuring a perpetually stable codebase.


Continuous Delivery (CD)

Moving beyond CI, Continuous Delivery concentrates on automating the comprehensive release process up to the production environment. Following successful integration and testing, the software undergoes an automated preparation phase for release. The release artifacts are meticulously assembled, ready for deployment. Notably, this phase stops short of automatic deployment to production, allowing for manual intervention to align with specific release strategies and compliance requirements.

CICD for React Native

Continuous Integration (CI):

  1. Use a version control system (e.g., Git).
  2. Integrate CI tools (e.g., Jenkins, Travis CI, GitHub Actions) to trigger automated builds and tests on code changes.
  3. Set up unit tests, linting, and static code analysis in your CI pipeline.


Continuous Delivery (CD):

  1. Automate the packaging and distribution process using tools like Fastlane or custom scripts.
  2. Deploy to beta testing services (e.g., TestFlight, Firebase App Distribution) for testing.

1. Version control system (Git)

A version control system helps manage and track changes to your source code over time. It allows multiple developers to work collaboratively on a project, providing a structured way to organize, save, and retrieve different versions of the codebase. Git is one of the most widely used version control systems.

In the context of CI/CD, a version control system is crucial because it provides a structured and organized way to manage code changes. CI/CD systems can monitor the repository for changes and trigger automated workflows (like building and testing) based on those changes.

2. Integrate CI tools (Github actions)

CI tools

Integrating Continuous Integration (CI) tools, such as Jenkins, Travis CI, or GitHub Actions, is crucial for automating the build and testing processes in software development. These tools monitor version control repositories for code changes and automatically trigger predefined workflows upon detecting modifications. Automated builds ensure that the code is compiled and packaged consistently, while automated tests verify its correctness. This integration minimizes manual intervention, accelerates development cycles, and enhances code quality by swiftly identifying and addressing issues during the early stages of development.


Feature

Jenkins

Travis CI

GitHub Actions

Integration with Git

Yes

Yes

Native(GitHub)

Configuration

Jenkinsfile (Groovy-based DSL)

.travis.yml (YAML)

YAML

Trigger Events

Wide range of events and triggers

Push, Pull Request, Tag

Push, Pull Request, Tag, etc.

Ease of Use

Requires setup and maintenance

Simplified setup for common tasks

Integrated with GitHub, easy setup

Cost

Free (self-hosted), Plugins may have costs

Free for public repositories, Paid plans for private repositories

Free for public repositories, Free minutes for private repositories, Additional minutes available for purchase.


GitHub Actions is a feature of GitHub that enables you to automate workflows directly within your repository. It allows you to define custom CI/CD processes, automate tasks, and respond to events such as code pushes, pull requests, and more.

CI/CD in flow React Native

Installation

Create a React Native project with latest version of react native.

1npx react-native@latest init article_CICD


Install required packages:

1npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer


Configuration


Changes in babel.config.js

1module.exports = {
2  presets: ['module:@react-native/babel-preset'],
3};


Changes in package.json

1 "scripts": {
2    "android": "react-native run-android",
3    "ios": "react-native run-ios",
4    "lint": "eslint .",
5    "start": "react-native start",
6    "test": "jest"
7  },


Make a simple Login Screen for testing:

We will just do the code in our main App.tsx file.


App.tsx

1/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 */
2
3import React, {useState} from 'react';
4import {
5  Linking,
6  StyleSheet,
7  Text,
8  TextInput,
9  TouchableOpacity,
10  View,
11} from 'react-native';
12
13const WelcomeScreen = () => {
14  return (
15    <View style={styles.view}>
16      <Text style={styles.welcomeText}>
17        Welcome to{' '}
18        <Text
19          style={{color: 'rgb(171, 91, 85)', textDecorationLine: 'underline'}}
20          onPress={() => Linking.openURL('https://code-b.dev')}>
21          CODEB
22        </Text>
23      </Text>
24    </View>
25  );
26};
27
28const LoginScreen = (): React.JSX.Element => {
29  const [username, setUsername] = useState('');
30  const [password, setPassword] = useState('');
31  const [message, setMessage] = useState('');
32  const handleSubmitPress = () => {
33    if (username === '' || password === '') {
34      setMessage('Please provide all values');
35    } else {
36      if (username === 'codeb@gmail.com' && password === 'Password@1234') {
37        setMessage('SUCCESS');
38      } else {
39        setMessage('INCORRECT CREDENTIAL');
40      }
41    }
42  };
43  return (
44    <View>
45      <TextInput
46        placeholder="Enter username"
47        autoCapitalize="none"
48        id="username"
49        keyboardType="email-address"
50        onChangeText={setUsername}
51      />
52      <TextInput
53        id="password"
54        placeholder="Enter Password"
55        autoCapitalize="none"
56        keyboardType="default"
57        onChangeText={setPassword}
58      />
59      <TouchableOpacity activeOpacity={0.5} onPress={handleSubmitPress}>
60        <Text>LOGIN</Text>
61      </TouchableOpacity>
62      {message === 'SUCCESS' ? (
63        <Text>
64          <WelcomeScreen />{' '}
65        </Text>
66      ) : (
67        <View>
68          <Text>{message}</Text>
69        </View>
70      )}
71    </View>
72  );
73};
74
75function App(): React.JSX.Element {
76  return <LoginScreen />;
77}
78
79const styles = StyleSheet.create({
80  view: {
81    flex: 1,
82    width: 'auto',
83    display: 'flex',
84    justifyContent: 'center',
85    alignItems: 'center',
86  },
87  welcomeText: {
88    fontSize: 28,
89  },
90});
91
92export default App;


Create Test functions:

Create a folder name __tests__ , create a new App.test.js inside this folder.


App.test.js

1// App.test.js or App.test.tsx
2
3import React from 'react';
4import {render, fireEvent} from '@testing-library/react-native';
5import App from '../App';
6
7describe('App', () => {
8  it('renders LoginScreen initially', () => {
9    const {getByPlaceholderText, getByText} = render(<App />);
10
11    // Ensure login elements are present
12    expect(getByPlaceholderText('Enter username')).toBeTruthy();
13    expect(getByPlaceholderText('Enter Password')).toBeTruthy();
14    expect(getByText('LOGIN')).toBeTruthy();
15  });
16
17  it('displays an error message for empty login', () => {
18    const {getByText} = render(<App />);
19
20    // Trigger login without entering values
21    fireEvent.press(getByText('LOGIN'));
22
23    // Ensure error message is displayed
24    expect(getByText('Please provide all values')).toBeTruthy();
25  });
26
27  it('displays a success message for correct login', () => {
28    const {getByPlaceholderText, getByText} = render(<App />);
29
30    // Enter correct username and password
31    fireEvent.changeText(
32      getByPlaceholderText('Enter username'),
33      'codeb@gmail.com',
34    );
35    fireEvent.changeText(
36      getByPlaceholderText('Enter Password'),
37      'Password@1234',
38    );
39
40    // Trigger login
41    fireEvent.press(getByText('LOGIN'));
42
43    // Ensure success message is displayed
44    expect(getByText('CODEB')).toBeTruthy();
45  });
46
47  it('displays an error message for incorrect login', () => {
48    const {getByPlaceholderText, getByText} = render(<App />);
49
50    // Enter incorrect username and password
51    fireEvent.changeText(
52      getByPlaceholderText('Enter username'),
53      'incorrect@gmail.com',
54    );
55    fireEvent.changeText(
56      getByPlaceholderText('Enter Password'),
57      'IncorrectPassword',
58    );
59
60    // Trigger login
61    fireEvent.press(getByText('LOGIN'));
62
63    // Ensure error message is displayed
64    expect(getByText('INCORRECT CREDENTIAL')).toBeTruthy();
65  });
66});


1. Initial Rendering Test:

  • The first test ensures that the App component initially renders the login screen.
  • It checks if the elements related to entering a username, entering a password, and the login button are present.

2. Empty Login Test:

  • This test checks if the application displays an error message when attempting to log in without entering any values.
  • It triggers a button press on the login button without providing any username or password and checks if the expected error message is displayed.

3. Successful Login Test:

  • This test simulates a successful login by entering correct username and password values.
  • It uses the fireEvent.changeText function to update the input fields, triggers a login button press, and then checks if the expected success message is displayed.

4. Incorrect Login Test:

  • This test checks if the application displays an error message for an incorrect login attempt.
  • It enters incorrect username and password values, triggers a login button press, and checks if the expected error message is displayed.


Running a test:

Run npm test to run jest testing of created test.

1> articleCICD@0.0.1 test
2> jest
3
4 PASS __tests__/App.test.js (9.081 s)
5 App
6 √ renders LoginScreen initially (5803 ms)
7 √ displays an error message for empty login (13 ms)
8 √ displays a success message for correct login (11 ms)
9 √ displays an error message for incorrect login (7 ms)
10
11Test Suites: 1 passed, 1 total
12Tests: 4 passed, 4 total
13Snapshots: 0 total
14Time: 15.238 s
15Ran all test suites.


We can also do the e2e testing by using detox. Detox is a gray-box end-to-end testing framework for React Native applications. It is specifically designed for mobile app testing and focuses on simulating user interactions and testing the application's behavior in a real-world environment. Detox allows developers to write and execute tests that interact with the UI components of a React Native app, performing actions such as tapping buttons, entering text, and navigating between screens. By running these tests on simulator/emulator or real devices, Detox helps ensure the reliability and functionality of the app across different platforms and devices. It also provides features for asynchronous testing and handling complex scenarios, making it a powerful tool for maintaining the quality and stability of React Native applications.

To learn more about detox testing in react native, visit https://wix.github.io/Detox/docs/introduction/getting-started

CICD Pipeline

CI (Continuous Integeration) testing stage in Github Actions:

Create .github/workflows directory at root directory of project. create main.yml file for writing github actions command.

1name: CI
2
3on:
4  push:
5    branches:
6      - master
7
8jobs:
9  build:
10    runs-on: ubuntu-latest
11
12    steps:
13      - name: Checkout Repository
14        uses: actions/checkout@v2
15
16      - name: Setup Node.js
17        uses: actions/setup-node@v2
18        with:
19          node-version: 18.19.0
20
21      - name: Install dependencies
22        run: npm install
23
24      - name: Run tests
25        run: npm test


This is a GitHub Actions workflow named "CI" triggered on every push to the "master" branch. The workflow runs on an Ubuntu environment and consists of four steps:

  1. Checkout Repository: It checks out the latest version of the repository using the actions/checkout action.
  2. Setup Node.js: It sets up Node.js on the environment, specifying version 18.19.0 with the actions/setup-node action.
  3. Install dependencies: It installs project dependencies using the npm install command.
  4. Run tests: It executes tests using the npm test command. This workflow is designed for continuous integration, automatically building and testing the project whenever changes are pushed to the master branch. The workflow helps ensure code quality and catches potential issues early in the development process.


CD (Continuous Delivery) :

Add following jobs in main.yml

1  build_android:
2    runs-on: ubuntu-latest
3
4    needs: build
5
6    steps:
7      - name: Checkout Repository
8        uses: actions/checkout@v2
9
10      - name: Setup Java 17
11        uses: actions/setup-java@v2
12        with:
13          distribution: 'temurin'
14          java-version: '17'
15
16      - name: Setup Node.js
17        uses: actions/setup-node@v2
18        with:
19          node-version: 18.19.0
20
21      - name: Navigate to Project Root
22        run: cd $GITHUB_WORKSPACE
23
24      - name: Install Dependencies
25        run: npm install
26
27      - name: Build Android App Bundle
28        run: |
29          mkdir -p android/app/build/intermediates/assets/release/
30          npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/build/intermediates/assets/release/index.android.bundle --assets-dest android/app/build/intermediates/res/merged/release
31
32      - name: Build Android Release
33        run: |
34          cd android
35          chmod +x gradlew
36          ./gradlew clean
37          ./gradlew bundleRelease -Pkeystore.password=${{ secrets.KEYSTORE_PASSWORD }}
38
39  download_bundle:
40    runs-on: ubuntu-latest
41
42    needs: build_android
43
44    steps:
45      - name: Download Android App Bundle
46        run: |
47          mkdir -p $GITHUB_WORKSPACE/dist
48          cp $GITHUB_WORKSPACE/android/app/build/outputs/bundle/release/app-release.aab $GITHUB_WORKSPACE/dist
49
50      - name: Archive Bundle
51        uses: actions/upload-artifact@v2
52        with:
53          name: android-bundle
54          path: $GITHUB_WORKSPACE/dist


1. build_android Job:

  • Runs on: Ubuntu latest.
  • Depends on: Another job named "build," indicating it requires the completion of the "build" job before starting.
  • Steps:

i. Checks out the repository.

ii. Sets up Java 17 and Node.js 18.19.0.

iii. Navigates to the project root directory.

iv. Installs project dependencies.

v. Builds the Android App Bundle:

a. Creates necessary directories.

b .Uses npx react-native bundle to bundle the JavaScript code for Android.

c. Sets up the Android project using Gradle and builds the release version, applying a keystore password from GitHub secrets.

2. download_bundle Job:

  • Runs on: Ubuntu latest.
  • Depends on: The completion of the "build_android" job.
  • Steps:

i. Downloads the Android App Bundle from the build output and saves it to the dist directory.

ii. Archives the downloaded bundle using the actions/upload-artifact action, making it available for further deployment or distribution.


Before running this workflow make sure you have added secrets variable in your GitHub repository.

We are triggering above CICD pipeline on branch master whenever someone push the code. we can make it for PR (pull request) whenever someone raise the PR on master branch.

Conclusion

This streamlined CI/CD pipeline triggers on master branch pushes, featuring three jobs: build, build_android, and download_bundle. The build job ensures code quality through Node.js tests. The subsequent build_android job, dependent on the build completion, orchestrates Java 17 and Node.js setup, installs dependencies, and builds the Android App Bundle. It safeguards sensitive data using GitHub secrets. Finally, the download_bundle job, contingent on build_android, archives the generated bundle for potential deployments. This succinct pipeline automates end-to-end processes, enhancing the development workflow for React Native Android apps.