Setting Up a Complete CI/CD Pipeline for React Using GitHub Actions
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
- Build and Setup: The
build
job initializes the repository and installs dependencies. It then runs linting and type checks to catch errors early. - Unit Tests: The
unit_tests
job runs Jest-based unit tests to verify component functionality in isolation. - Integration Tests: The
integration_tests
job runs integration tests using Jest to verify component interactions. - 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.