Directories - ripplearc/construculator-app GitHub Wiki

Testing Guidelines

This document outlines our testing structure and conventions. Following these guidelines ensures your tests are picked up by CI/CD pipelines and local scripts.

Directory Structure

All tests must be placed under either test/features/ or test/libraries/ following this structure:

test/
β”œβ”€β”€ features/
β”‚   └── <feature_name>/
β”‚       β”œβ”€β”€ units/          # Unit tests (blocs, providers, etc.)
β”‚       β”œβ”€β”€ widgets/        # Widget tests
β”‚       β”œβ”€β”€ screenshots/    # Golden/screenshot tests
β”‚       └── mutations/      # Mutation test configs (.xml)
└── libraries/
    └── <library_name>/
        β”œβ”€β”€ units/          # Unit tests
        └── mutations/      # Mutation test configs (.xml)

Real Examples

test/features/auth/widgets/widgets/auth_footer_test.dart
test/features/auth/screenshots/auth_header_screenshot_test.dart
test/features/auth/mutations/create_account_bloc.xml
test/libraries/config/units/app_config_test.dart
test/libraries/supabase/mutations/fake_supabase_wrapper.xml

Detailed Subfolder Organization

Units Tests (units/)

Rule: Mirror your implementation structure as much as possible. If your feature has data/, domain/, and presentation/ layers, your tests should follow the same structure.

Common Subfolders in units/:

units/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ data_source/      # Remote/local data source tests
β”‚   β”œβ”€β”€ models/           # DTO/model tests
β”‚   └── repositories/     # Repository implementation tests
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/         # Entity tests
β”‚   β”œβ”€β”€ usecases/         # Use case tests
β”‚   └── validation/       # Domain validation logic tests
β”œβ”€β”€ presentation/         # Non-widget presentation logic
β”œβ”€β”€ blocs/                # Bloc/Cubit tests
|__ fakes/                # Fake implementations for testing

Real-World Examples:

Estimations Feature (Clean Architecture):

test/features/estimations/units/
β”œβ”€β”€ blocs/
β”‚   β”œβ”€β”€ add_cost_estimation_bloc_test.dart
β”‚   β”œβ”€β”€ cost_estimation_list_bloc_test.dart
β”‚   └── delete_cost_estimation_bloc_test.dart
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ data_source/
β”‚   β”‚   └── remote_cost_estimation_data_source_test.dart
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── cost_estimate_dto_test.dart
β”‚   └── repositories/
β”‚       └── cost_estimation_repository_impl_test.dart
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/
β”‚   └── usecases/
β”‚       └── add_cost_estimation_usecase_test.dart
└── fakes/
    └── fake_cost_estimation_repository_test.dart

This mirrors:

lib/features/estimation/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ data_source/
β”‚   β”œβ”€β”€ models/
β”‚   └── repositories/
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/
β”‚   └── usecases/
└── presentation/
    └── bloc/

Project Feature (presentation layer):

test/features/project/units/
β”œβ”€β”€ blocs/
β”‚   └── get_project_bloc_test.dart
└── presentation/
    └── project_ui_provider_impl_test.dart

Auth Library (library example):

test/libraries/auth/units/
β”œβ”€β”€ data/
β”‚   └── models/
β”‚       β”œβ”€β”€ auth_user_test.dart
β”‚       └── auth_credential_test.dart
β”œβ”€β”€ domain/
β”‚   └── validation/
β”‚       └── auth_validation_test.dart
β”œβ”€β”€ repositories/
β”‚   └── supabase_auth_repository_test.dart
β”œβ”€β”€ fakes/
β”‚   β”œβ”€β”€ fake_auth_manager_test.dart
β”‚   β”œβ”€β”€ fake_auth_repository_test.dart
β”‚   └── fake_auth_notifier_test.dart
β”œβ”€β”€ auth_manager_test.dart
└── auth_notifier_test.dart

Key Principle: If you're testing lib/features/estimation/data/repositories/foo.dart, the test goes in test/features/estimations/units/data/repositories/foo_test.dart.

Widget Tests (widgets/)

Widget tests are organized by widget type.

Common Subfolders in widgets/:

widgets/
β”œβ”€β”€ pages/       # Full page widgets
└── widgets/     # Reusable component widgets

Real-World Examples:

Auth Feature:

test/features/auth/widgets/
β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ create_account_page_test.dart
β”‚   β”œβ”€β”€ enter_password_page_test.dart
β”‚   β”œβ”€β”€ forgot_password_page_test.dart
β”‚   β”œβ”€β”€ login_with_email_page_test.dart
β”‚   β”œβ”€β”€ register_with_email_page_test.dart
β”‚   └── set_new_password_page_test.dart
└── widgets/
    β”œβ”€β”€ auth_footer_test.dart
    β”œβ”€β”€ auth_header_test.dart
    β”œβ”€β”€ auth_provider_buttons_test.dart
    β”œβ”€β”€ error_widget_builder_test.dart
    β”œβ”€β”€ otp_verification_sheet_test.dart
    └── terms_and_conditions_section_test.dart

This tests widgets in:

lib/features/auth/presentation/
β”œβ”€β”€ pages/          # Page-level widgets
└── widgets/        # Smaller reusable widgets

Screenshot Tests (screenshots/)

Screenshot tests are flat - no subfolders. All golden tests for a feature live directly in the screenshots directory.

test/features/auth/screenshots/
β”œβ”€β”€ auth_footer_screenshot_test.dart
β”œβ”€β”€ auth_header_screenshot_test.dart
β”œβ”€β”€ auth_provider_buttons_screenshot_test.dart
β”œβ”€β”€ error_widget_builder_screenshot_test.dart
β”œβ”€β”€ forgot_password_header_screenshot_test.dart
β”œβ”€β”€ otp_verification_sheet_screenshot_test.dart
└── terms_and_conditions_section_screenshot_test.dart

Golden files are stored alongside tests:

test/features/project/screenshots/
β”œβ”€β”€ goldens/
β”‚   └── project_header_app_bar/
β”‚       └── 390.0x56.0/
β”‚           β”œβ”€β”€ project_header_app_bar_long_name.png
β”‚           β”œβ”€β”€ project_header_app_bar_no_avatar.png
β”‚           └── project_header_app_bar_normal.png
└── project_header_app_bar_screenshot_test.dart

Mutation Tests (mutations/)

Mutation configs are flat - no subfolders. All .xml configs live directly in the mutations directory.

test/features/auth/mutations/
β”œβ”€β”€ create_account_bloc.xml
β”œβ”€β”€ create_account_page_mutations.xml
β”œβ”€β”€ enter_password_bloc.xml
β”œβ”€β”€ enter_password_page_mutations.xml
β”œβ”€β”€ forgot_password_bloc.xml
β”œβ”€β”€ login_with_email_bloc.xml
└── ...

Why flat? Mutation testing runs on specific files, and we don't need complex organization since configs are named descriptively.

Naming Conventions

Note: Test files usually shoulld follow Flutter conventions:

  • Units and widgets: Test file name = source file name + _test.dart
  • Example: auth_footer.dart β†’ auth_footer_test.dart
  • Screenshots: Test file name = source file name + _screenshot_test.dart
  • Example: auth_footer.dart β†’ auth_footer_screenshot_test.dart
  • Mutation configs: Test file name = source file name + _mutations.xml
  • Example: auth_footer.dart β†’ auth_footer_mutations.xml

Why This Structure Matters

Our scripts and CI/CD (scripts/run_check.sh and codemagic.yaml) are configured to find tests in these specific locations:

# Pre-check runs only changed tests
test/features/**/units/*.dart
test/features/**/widgets/*.dart
test/libraries/**/units/*.dart

# Comprehensive check runs all tests
test/features/**/screenshots/*.dart
test/features/**/mutations/*.xml
test/libraries/**/mutations/*.xml

⚠️ If you place tests outside these paths, they might not run in CI/CD and will be skipped in coverage.

Test Types Summary

Unit Tests (units/)

  • Business logic and state management
  • Pure Dart logic tests
  • Common subfolders: data/, domain/, presentation/, blocs/, fakes/, helpers/
  • Key Principle: Mirror your implementation structure

Widget Tests (widgets/)

  • Widget behavior and interaction
  • Widget state changes
  • Basic UI logic without golden comparison
  • Subfolders: pages/ and widgets/

Screenshot Tests (screenshots/)

  • Golden file comparisons
  • Visual regression testing
  • Use --update-goldens flag to update reference images
  • Flat structure (no subfolders)
  • Shows golden file organization

Important Notes:

  • Screenshot tests do not contribute to code coverage metrics
  • Test specific UI states and variations (e.g., loading, error, empty states)
  • Avoid full-page screenshotsβ€”isolate the critical user journey (CUJ) of individual widgets
  • Don't overtest, one screenshot per widget state + some edge cases is sufficient (no need for multiple redundant tests)

Mutation Tests (mutations/)

  • XML config files for mutation testing
  • Tests run only when mutation configs change
  • Validates test suite quality
  • Flat structure with descriptive names

Coverage Requirements

  • Target: 95% code coverage (ARC_CODE_COVERAGE_TARGET=95)
  • Excludes generated files (*.g.dart, *.freezed.dart, l10n/**)
  • Both pre-check and comprehensive-check enforce this threshold
  • Calculated based on unit and widget tests

Running Tests Locally

# Pre-check (changed files only)
./scripts/run_check.sh --pre

# Comprehensive check (all tests + builds)
./scripts/run_check.sh --comp

# Both checks
./scripts/run_check.sh --all

# Mutation tests only
./scripts/run_check.sh --mutations

# Specify target branch
./scripts/run_check.sh --pre --target develop
⚠️ **GitHub.com Fallback** ⚠️