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.
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.
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.
Continuous Integration (CI):
Continuous Delivery (CD):
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.
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.
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.
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
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:
App
component initially renders the login screen.2. Empty Login Test:
3. Successful Login Test:
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:
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
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:
actions/checkout
action.actions/setup-node
action.npm install
command.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:
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:
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.
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.