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

img
vijesh_profile
Vijesh ChoudhariSoftware Developerauthor linkedin
Published On
Updated On
Table of Content
up_arrow

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.

npx react-native@latest init article_CICD


Install required packages:

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


Configuration


Changes in babel.config.js

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


Changes in package.json

 "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest"
  },


Make a simple Login Screen for testing:

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


App.tsx

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 */

import React, {useState} from 'react';
import {
  Linking,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';

const WelcomeScreen = () => {
  return (
    <View style={styles.view}>
      <Text style={styles.welcomeText}>
        Welcome to{' '}
        <Text
          style={{color: 'rgb(171, 91, 85)', textDecorationLine: 'underline'}}
          onPress={() => Linking.openURL('https://code-b.dev')}>
          CODEB
        </Text>
      </Text>
    </View>
  );
};

const LoginScreen = (): React.JSX.Element => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [message, setMessage] = useState('');
  const handleSubmitPress = () => {
    if (username === '' || password === '') {
      setMessage('Please provide all values');
    } else {
      if (username === 'codeb@gmail.com' && password === 'Password@1234') {
        setMessage('SUCCESS');
      } else {
        setMessage('INCORRECT CREDENTIAL');
      }
    }
  };
  return (
    <View>
      <TextInput
        placeholder="Enter username"
        autoCapitalize="none"
        id="username"
        keyboardType="email-address"
        onChangeText={setUsername}
      />
      <TextInput
        id="password"
        placeholder="Enter Password"
        autoCapitalize="none"
        keyboardType="default"
        onChangeText={setPassword}
      />
      <TouchableOpacity activeOpacity={0.5} onPress={handleSubmitPress}>
        <Text>LOGIN</Text>
      </TouchableOpacity>
      {message === 'SUCCESS' ? (
        <Text>
          <WelcomeScreen />{' '}
        </Text>
      ) : (
        <View>
          <Text>{message}</Text>
        </View>
      )}
    </View>
  );
};

function App(): React.JSX.Element {
  return <LoginScreen />;
}

const styles = StyleSheet.create({
  view: {
    flex: 1,
    width: 'auto',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  },
  welcomeText: {
    fontSize: 28,
  },
});

export default App;


Create Test functions:

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


App.test.js

// App.test.js or App.test.tsx

import React from 'react';
import {render, fireEvent} from '@testing-library/react-native';
import App from '../App';

describe('App', () => {
  it('renders LoginScreen initially', () => {
    const {getByPlaceholderText, getByText} = render(<App />);

    // Ensure login elements are present
    expect(getByPlaceholderText('Enter username')).toBeTruthy();
    expect(getByPlaceholderText('Enter Password')).toBeTruthy();
    expect(getByText('LOGIN')).toBeTruthy();
  });

  it('displays an error message for empty login', () => {
    const {getByText} = render(<App />);

    // Trigger login without entering values
    fireEvent.press(getByText('LOGIN'));

    // Ensure error message is displayed
    expect(getByText('Please provide all values')).toBeTruthy();
  });

  it('displays a success message for correct login', () => {
    const {getByPlaceholderText, getByText} = render(<App />);

    // Enter correct username and password
    fireEvent.changeText(
      getByPlaceholderText('Enter username'),
      'codeb@gmail.com',
    );
    fireEvent.changeText(
      getByPlaceholderText('Enter Password'),
      'Password@1234',
    );

    // Trigger login
    fireEvent.press(getByText('LOGIN'));

    // Ensure success message is displayed
    expect(getByText('CODEB')).toBeTruthy();
  });

  it('displays an error message for incorrect login', () => {
    const {getByPlaceholderText, getByText} = render(<App />);

    // Enter incorrect username and password
    fireEvent.changeText(
      getByPlaceholderText('Enter username'),
      'incorrect@gmail.com',
    );
    fireEvent.changeText(
      getByPlaceholderText('Enter Password'),
      'IncorrectPassword',
    );

    // Trigger login
    fireEvent.press(getByText('LOGIN'));

    // Ensure error message is displayed
    expect(getByText('INCORRECT CREDENTIAL')).toBeTruthy();
  });
});


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.

> articleCICD@0.0.1 test
> jest

PASS __tests__/App.test.js (9.081 s)
App
√ renders LoginScreen initially (5803 ms)
√ displays an error message for empty login (13 ms)
√ displays a success message for correct login (11 ms)
√ displays an error message for incorrect login (7 ms)

Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 15.238 s
Ran 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.

name: CI

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 18.19.0

      - name: Install dependencies
        run: npm install

      - name: Run tests
        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

  build_android:
    runs-on: ubuntu-latest

    needs: build

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Setup Java 17
        uses: actions/setup-java@v2
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 18.19.0

      - name: Navigate to Project Root
        run: cd $GITHUB_WORKSPACE

      - name: Install Dependencies
        run: npm install

      - name: Build Android App Bundle
        run: |
          mkdir -p android/app/build/intermediates/assets/release/
          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

      - name: Build Android Release
        run: |
          cd android
          chmod +x gradlew
          ./gradlew clean
          ./gradlew bundleRelease -Pkeystore.password=${{ secrets.KEYSTORE_PASSWORD }}

  download_bundle:
    runs-on: ubuntu-latest

    needs: build_android

    steps:
      - name: Download Android App Bundle
        run: |
          mkdir -p $GITHUB_WORKSPACE/dist
          cp $GITHUB_WORKSPACE/android/app/build/outputs/bundle/release/app-release.aab $GITHUB_WORKSPACE/dist

      - name: Archive Bundle
        uses: actions/upload-artifact@v2
        with:
          name: android-bundle
          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.

Schedule a call now
Start your offshore web & mobile app team with a free consultation from our solutions engineer.

We respect your privacy, and be assured that your data will not be shared