## Learning Objectives

By the end of this chapter, you will be able to:

- Explain testing types in Flutter: unit, widget, and integration tests
- Write clear, maintainable tests for models, providers, and widgets
- Organize tests and use test doubles (mocks, fakes) appropriately
- Run tests locally and interpret results and coverage reports
- Set up a CI pipeline to run analysis, tests, and build artifacts automatically

1

## Testing Pyramid and Scope

Understanding the testing pyramid helps you write effective tests.

### Unit Tests

Fast, pure logic (parsers, validators, services without I/O), run in milliseconds.

### Widget Tests

Verify a widget's UI and interaction in isolation (pumpWidget, find, tap, pumpAndSettle).

### Integration Tests

Full app flows on device/emulator (user journeys, navigation, network stubs).

Rule: Many unit tests, fewer widget tests, minimal but strategic integration tests.

2

## Test Setup and Tools

Proper tooling makes testing efficient and reliable.

### Testing Tools

- Use the `flutter_test` package (included).
- Use mockito or mocktail for mocks; use test doubles (fakes) when behavior is simple.
- Use golden tests for visual regressions when UI consistency is critical.
- Use flutter_driver or integration_test (preferred) for end-to-end tests.

### Add Dev Dependencies to pubspec.yaml (Conceptual)

    dev_dependencies:
      flutter_test:
      mockito: # or mocktail
      integration_test:
      flutter_lints:

3

## Unit Testing Patterns

Following patterns makes unit tests clear and maintainable.

### Best Practices

- Test pure functions and model parsing with a variety of inputs including edge cases.
- Arrange-Act-Assert structure for clarity.
- Use setUp/tearDown for shared initialization.

### Example Checks (Conceptual)

- Model.fromJson should parse valid JSON and throw FormatException on invalid JSON.
- Validator functions return expected error strings or null.

### Best Practices

- Keep tests deterministic and fast.
- Avoid real network or file I/O; inject services and use mocks/fakes.

4

## Widget Testing Basics

Widget tests verify UI behavior and interactions.

### Widget Testing Process

- Pump the widget under test with required providers and dependencies.
- Use WidgetTester to interact (enterText, tap, pumpAndSettle).
- Assert visible text, widget counts, widget states, and navigation.

### Common Patterns

- Wrap widgets with MaterialApp when navigation, themes, or localization are required.
- Provide mocked providers via Provider scope or by injecting fake services.

### Example Flow

Pump HomeScreen with test provider returning sample items. Tap first item, await pumpAndSettle(), assert DetailsScreen content visible.

5

## Integration Testing Essentials

Integration tests verify complete user journeys.

### Integration Testing

- Write tests using integration_test package; run on emulator or real device.
- Drive full user journeys: login → navigate → perform action → verify API call or UI reaction.
- Use network stubs or run a local test server for reliable results.

### Runner Pattern

Create an integration_test/main_test.dart that boots the app and uses the WidgetTester-like API to drive interactions.

6

## Mocking and Test Doubles

Test doubles help isolate code under test.

### Mocks

Assert interactions (method called with arguments). Use mocktail/mockito.

### Fakes

Lightweight in-memory implementations (e.g., FakeCacheService using an in-memory map).

### Stubs

Return canned responses for specific calls.

Guideline: Prefer fakes for stateful services to keep tests readable and reliable; use mocks when verifying interaction counts or arguments matters.

7

## Test Organization and Naming

Well-organized tests are easier to maintain and understand.

### Directory Structure

- `test/unit/` — model and service tests
- `test/widget/` — widget tests
- `integration_test/` — end-to-end tests

### Naming Conventions

- Name tests to explain behavior: `should_parse_valid_json`, `shows_error_on_network_failure`.
- Keep test files parallel to lib structure for discoverability (e.g., `test/models/course_test.dart`).

8

## Code Coverage and Metrics

Coverage metrics help identify untested code.

### Generate Coverage

- `flutter test --coverage`
- Convert lcov.info to HTML with genhtml (CI artifact).

Set realistic coverage goals (e.g., 70–90%) and prioritize critical logic coverage over trivial getters.

9

## Continuous Integration (CI) Basics

CI automates testing and quality checks.

### CI Tasks

Static analysis, format check, unit & widget tests, integration tests (optional), build artifacts (apk/aab) for release channels.

### CI Providers

Choose CI provider: GitHub Actions, GitLab CI, Bitrise, Codemagic. Example below uses GitHub Actions.

### Core CI Steps

1. Checkout code
2. Install Flutter (version pinned)
3. flutter pub get
4. flutter analyze
5. flutter test --coverage
6. Build (optional): flutter build apk --release
7. Upload artifacts and test reports

10

## Example GitHub Actions Workflow (Conceptual)

GitHub Actions provides a straightforward CI/CD solution.

### Workflow Configuration

- Trigger: push, pull_request on main branch.
- Jobs: analyze-and-test, build (optional), integration-tests (optional on matrix devices).

### High-Level Job Steps

- Set up Flutter
- Run flutter pub get
- Run flutter analyze
- Run flutter test --coverage and upload coverage artifact
- Build APK and store as artifact

Notes: Use prebuilt actions for Flutter, cache pub packages, and fail fast on lint/test failures.

11

## CI Best Practices and Flakiness Reduction

Following best practices ensures reliable CI pipelines.

### Best Practices

- Keep tests deterministic: seed random values, avoid time-based assumptions, and use fake async when necessary.
- Split slow integration tests into a separate job that runs nightly or only on main branch merges.
- Cache dependencies to speed up pipelines.
- Upload test artifacts (logs, coverage, test reports) for debugging failures.

12

## Automated Quality Gates

Quality gates enforce code standards automatically.

### Quality Gates

- Enforce static analysis (flutter analyze) and lints via analysis_options.yaml.
- Fail PR pipelines if tests or analysis fail.
- Optionally add code coverage threshold checks and block merges when critical coverage drops.

13

## Local Developer Workflow

Efficient local workflows catch issues early.

### Local Testing

- Run tests frequently: `flutter test`
- Use `flutter test --coverage` and a local coverage viewer or service to inspect.
- Use pre-commit hooks to run format and basic checks (dart format, flutter analyze) to catch issues early.

14

## Exercises

Practice what you've learned with these exercises:

### 1. Unit tests for models

Write tests for Course.fromJson with valid, missing fields, and malformed JSON.

### 2. Widget test for CourseList

Pump a CourseList with a fake provider, assert visible course cards, tap a card, and verify navigation intent.

### 3. CI pipeline (GitHub Actions)

Create a basic GitHub Actions workflow that runs analyze and tests on PRs. Commit workflow file and verify CI run on push.

15

## Session Assignment

Implement and run the three exercises above. Add workflow YAML to repository, ensure the pipeline passes on your branch, and include a short CI results screenshot and test coverage lcov.info as part of your submission.