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.