← Articles

Continuous integration for Flutter: running tests on every pull request

By Ann Tech · 31 March 2026

CI runs your tests automatically on every pull request. It's the safety net that catches regressions before they reach production. Here is a complete GitHub Actions setup for Flutter.

The core workflow

.github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          channel: 'stable'
          cache: true

      - name: Install dependencies
        run: flutter pub get

      - name: Verify formatting
        run: dart format --output=none --set-exit-if-changed .

      - name: Analyze
        run: flutter analyze --fatal-infos

      - name: Run tests
        run: flutter test --coverage

Adding a build verification step

Ensure the app compiles — catches broken imports and missing generated files:

  build-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          cache: true
      - run: flutter pub get
      - name: Build (debug)
        run: flutter build apk --debug --flavor dev -t lib/main_dev.dart

Caching pub packages

      - name: Cache pub packages
        uses: actions/cache@v4
        with:
          path: |
            ~/.pub-cache
            .dart_tool
          key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
          restore-keys: ${{ runner.os }}-pub-

With caching, flutter pub get drops from ~60 seconds to ~5 seconds on warm runs.

Branch protection rules

In GitHub → Repository → Settings → Branches → Add rule for main:

  • Require status checks to pass before merging
  • Select your CI job names
  • Require branches to be up to date before merging

This makes CI mandatory — no one can merge a failing pull request.

Passing secrets to build commands

      - name: Build production bundle
        run: |
          flutter build appbundle \
            --flavor production \
            -t lib/main.dart \
            --dart-define=API_BASE_URL=${{ secrets.PROD_API_URL }}

Add secrets in GitHub → Repository → Settings → Secrets and variables → Actions.

Matrix testing across Flutter versions

    strategy:
      matrix:
        flutter-version: ['3.22.0', '3.24.0']
    steps:
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ matrix.flutter-version }}

Common pitfalls

Not caching Flutter SDK. Without cache: true on subosito/flutter-action, every CI run downloads Flutter — adding 2–3 minutes. Always enable caching.

Only running unit tests. Widget tests and integration tests catch different bugs. Add widget tests to CI even if integration tests run in a separate pipeline.

Ignoring analyzer warnings. flutter analyze without --fatal-infos lets warnings accumulate silently. Enforce clean analysis from day one — fixing 200 warnings at once is much harder than preventing them.

Sign in to like, dislike, or report.

Continuous integration for Flutter: running tests on every pull request — ANN Tech