What is Mutation Testing?
Mutation testing evaluates the quality of your test suite by introducing small changes (mutations) to the source code and checking if the tests detect them.
How it works
-
A mutation tool creates small variations called mutants:
- Changing
> to >= or <
- Replacing
true with false
- Removing a function call
- Changing
+ to -
- Replacing a string with an empty string
-
For each mutant, the tool runs your test suite
-
If tests fail → mutant killed ✅ (tests caught the change)
-
If tests pass → mutant survived ❌ (tests missed the change)
A high mutation score (% of killed mutants) means your tests are effective. A low score means bugs could hide undetected.
Why it matters
Code coverage tells you which lines were executed — not whether tests actually verify correct behavior. Mutation testing answers: "If a bug were introduced here, would our tests catch it?"
Concrete examples from this codebase
| Original code |
Mutant |
What it reveals |
if (match.timeRemaining > 0) |
if (match.timeRemaining >= 0) |
Does the test check the zero boundary? |
fighter1Score += points |
fighter1Score -= points |
Does the test verify the score actually increased? |
status: MatchStatus.inProgress |
status: MatchStatus.finished |
Does the test assert the correct status? |
event.tags.add(['d', matchId]) |
removed |
Does the test verify Nostr event structure? |
Current State
- 15 source files in
lib/
- 2 test files:
test/widget_test.dart, test/features/match/models/match_test.dart
- No CI pipeline yet
- Key modules untested:
nostr_service.dart, key_manager.dart, relay_config_provider.dart, all providers
Recommended Tool: DCM (Dart Code Metrics)
DCM is the only actively maintained tool that supports mutation testing for Dart/Flutter. It includes:
- ✅ Dart/Flutter first-class support
- ✅ Mutation testing built-in (
dcm run-mutations)
- ✅ Multiple mutators (arithmetic, boolean, boundary, removal, etc.)
- ✅ HTML/JSON reports
- ✅ CI integration
- ✅ Configurable rules and exclusions
Alternatives Considered
| Tool |
Verdict |
| DCM |
✅ Best choice — only mature mutation testing tool for Dart |
| mutant (pub.dev) |
❌ Does not exist as a real package |
| StrykerJS |
❌ No Dart/Flutter support |
| Custom script |
❌ Fragile, not worth maintaining |
Implementation Plan
Phase 1: Setup and Baseline
-
Install DCM:
dart pub global activate dcm
-
Add analysis_options.yaml configuration:
dart_code_metrics:
mutations:
mutators:
- arithmetic
- boolean_literal
- conditional_boundary
- remove_statement
- negate_conditional
exclude:
- "lib/main.dart"
- "lib/shared/theme/**"
- "**/*.g.dart"
- "**/*.freezed.dart"
-
Run baseline:
dcm run-mutations lib/ --report-format=html
Phase 2: Expand Test Coverage for Key Modules
Priority order for writing tests (based on criticality):
| Module |
Priority |
Reason |
match.dart (model) |
✅ Has tests |
Verify mutation score |
match_control_provider.dart |
🔴 High |
Core scoring logic |
nostr_service.dart |
🔴 High |
Event publishing, data integrity |
key_manager.dart |
🟡 Medium |
Key generation/storage |
home_providers.dart |
🟡 Medium |
Event parsing, deduplication |
relay_config_provider.dart |
🟢 Low |
Settings persistence |
Phase 3: CI Integration
Create .github/workflows/mutation-testing.yaml:
name: Mutation Testing
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Weekly Monday 6:00 UTC
workflow_dispatch:
jobs:
mutation-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: flutter pub get
- run: dart pub global activate dcm
- name: Run mutation tests
continue-on-error: true # Non-blocking initially
run: dcm run-mutations lib/ --report-format=json --output=mutation-report.json
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: mutation-report
path: mutation-report.json
retention-days: 30
Acceptance Criteria
References
What is Mutation Testing?
Mutation testing evaluates the quality of your test suite by introducing small changes (mutations) to the source code and checking if the tests detect them.
How it works
A mutation tool creates small variations called mutants:
>to>=or<truewithfalse+to-For each mutant, the tool runs your test suite
If tests fail → mutant killed ✅ (tests caught the change)
If tests pass → mutant survived ❌ (tests missed the change)
A high mutation score (% of killed mutants) means your tests are effective. A low score means bugs could hide undetected.
Why it matters
Code coverage tells you which lines were executed — not whether tests actually verify correct behavior. Mutation testing answers: "If a bug were introduced here, would our tests catch it?"
Concrete examples from this codebase
if (match.timeRemaining > 0)if (match.timeRemaining >= 0)fighter1Score += pointsfighter1Score -= pointsstatus: MatchStatus.inProgressstatus: MatchStatus.finishedevent.tags.add(['d', matchId])Current State
lib/test/widget_test.dart,test/features/match/models/match_test.dartnostr_service.dart,key_manager.dart,relay_config_provider.dart, all providersRecommended Tool: DCM (Dart Code Metrics)
DCM is the only actively maintained tool that supports mutation testing for Dart/Flutter. It includes:
dcm run-mutations)Alternatives Considered
Implementation Plan
Phase 1: Setup and Baseline
Install DCM:
Add
analysis_options.yamlconfiguration:Run baseline:
Phase 2: Expand Test Coverage for Key Modules
Priority order for writing tests (based on criticality):
match.dart(model)match_control_provider.dartnostr_service.dartkey_manager.darthome_providers.dartrelay_config_provider.dartPhase 3: CI Integration
Create
.github/workflows/mutation-testing.yaml:Acceptance Criteria
References