Setting Up a Complete CI/CD Pipeline for React Using GitHub Actions

Santhosh Adiga U
4 min readOct 28, 2024

--

Photo by Roman Synkevych on Unsplash

Implementing a robust CI/CD (Continuous Integration and Continuous Deployment) pipeline is essential for maintaining the quality, stability, and speed of your software releases. In this article, we’ll walk through setting up an automated pipeline for a React project using GitHub Actions. This pipeline will automate integration tests (IT), mock testing, unit testing (UT), and end-to-end (E2E) tests, creating a streamlined deployment process.

Prerequisites and Initial Setup

Before setting up GitHub Actions, ensure:

  • You have a React application hosted on GitHub.
  • Your project is configured with Jest (for unit and integration tests) and Cypress or Playwright (for end-to-end tests).

Setting Up Jest for UT and IT

Install Jest if it’s not already included:

npm install --save-dev jest

Setting Up Cypress for E2E Testing

Install Cypress as a dev dependency:

npm install --save-dev cypress

Structuring GitHub Actions Workflow Files

GitHub Actions workflows are stored in the .github/workflows/ directory. Create a new workflow file named ci.yml to define the CI/CD pipeline.

File Structure for Testing

Organize your test files by type:

/src
├── components
├── utils
└── __tests__
├── unit
├── integration
└── e2e
  • unit/: Contains unit tests for individual components.
  • integration/: Includes tests that verify interactions between components.
  • e2e/: Contains Cypress E2E tests.

Writing the CI/CD Pipeline

Setting Up a Workflow File

In .github/workflows/ci.yml, we define steps for:

  • Installing dependencies
  • Running linting and type checks
  • Executing unit tests, integration tests, and mock tests
  • Running E2E tests
name: CI/CD Pipeline

on:
push:
branches:
- main
pull_request:
branches:
- main

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: '16'

- name: Install dependencies
run: npm install

- name: Run Lint
run: npm run lint

- name: Run Type Checks
run: npm run type-check

unit_tests:
runs-on: ubuntu-latest
needs: build

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

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

- name: Install dependencies
run: npm install

- name: Run Unit Tests
run: npm test -- --coverage

integration_tests:
runs-on: ubuntu-latest
needs: unit_tests

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

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

- name: Install dependencies
run: npm install

- name: Run Integration Tests
run: npm run test:integration

e2e_tests:
runs-on: ubuntu-latest
needs: integration_tests

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

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

- name: Install dependencies
run: npm install

- name: Run Cypress E2E Tests
run: npx cypress run

Explanation of Each Step

  1. Build and Setup: The build job initializes the repository and installs dependencies. It then runs linting and type checks to catch errors early.
  2. Unit Tests: The unit_tests job runs Jest-based unit tests to verify component functionality in isolation.
  3. Integration Tests: The integration_tests job runs integration tests using Jest to verify component interactions.
  4. End-to-End Tests: The e2e_tests job uses Cypress to simulate user flows and verify that the app behaves as expected from the user's perspective.

Triggering the Pipeline

The pipeline is triggered on any push or pull request to the main branch. For other branches, modify the branches key under on: push: as needed.

Sample Scripts for Different Test Types

Define test scripts in package.json to handle different types of tests:

"scripts": {
"test": "jest",
"test:unit": "jest __tests__/unit",
"test:integration": "jest __tests__/integration",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
}

In this example:

  • npm test runs all Jest tests.
  • npm run test:unit runs only unit tests.
  • npm run test:integration runs only integration tests.
  • npm run cypress:run runs Cypress E2E tests.

Running Mock Tests

Mock tests are essential for validating components that depend on external services or APIs. You can mock APIs using Jest’s mocking capabilities.

Example of a Mock Test for Fetching Movie Data

// MovieService.test.ts
import { fetchMovie } from './MovieService';

jest.mock('./MovieService');

test("fetches movie data successfully", async () => {
const mockMovieData = { id: 1, title: "Inception" };
fetchMovie.mockResolvedValueOnce(mockMovieData);

const result = await fetchMovie(1);
expect(result).toEqual(mockMovieData);
expect(fetchMovie).toHaveBeenCalledWith(1);
});

Adding Test Coverage Reporting

Adding coverage reporting provides insights into which parts of your codebase are thoroughly tested. Jest has built-in support for generating coverage reports.

In your package.json, update the test command to include coverage:

"scripts": {
"test": "jest --coverage"
}

GitHub Actions will now display a coverage report upon completion, making it easy to track test coverage over time.

This CI/CD pipeline setup provides a complete workflow that automates code quality checks, unit and integration tests, mock testing, and end-to-end testing in a React application. By following these best practices, you’ll ensure that every release is stable, performs as expected, and meets quality standards.

--

--

Santhosh Adiga U
Santhosh Adiga U

Written by Santhosh Adiga U

Founder of Anakramy ., dedicated to creating innovative AI-driven cybersecurity solutions.

Responses (1)