This guide covers how to write, run, and maintain tests in PetSphere.
flutter testflutter test --coverageView coverage report:
# macOS
open coverage/index.html
# Windows/Linux
# Open coverage/index.html in your browserflutter test test/models/pet_model_test.dartflutter test --name "PetModel"flutter test --watchtest/
├── models/ # Model unit tests
│ ├── pet_model_test.dart
│ ├── user_model_test.dart
│ └── ...
├── controllers/ # Controller state management tests
│ └── (to be added)
└── integration/ # Integration & E2E tests
└── app_test.dart
Models should have complete JSON roundtrip tests:
import 'package:flutter_test/flutter_test.dart';
import 'package:pet_dating_app/models/pet_model.dart';
void main() {
group('PetModel', () {
test('parses from JSON correctly', () {
final json = {
'id': 'pet-123',
'name': 'Fluffy',
// ... all fields
};
final pet = PetModel.fromJson(json);
expect(pet.id, 'pet-123');
expect(pet.name, 'Fluffy');
});
test('converts to JSON correctly', () {
final pet = PetModel(
id: 'pet-123',
name: 'Fluffy',
// ... required fields
);
final json = pet.toJson();
expect(json['id'], 'pet-123');
});
test('roundtrip serialization', () {
final original = PetModel(...);
final json = original.toJson();
final deserialized = PetModel.fromJson(json);
expect(deserialized.id, original.id);
expect(deserialized.name, original.name);
});
test('copyWith creates new instance', () {
final pet = PetModel(...);
final updated = pet.copyWith(name: 'NewName');
expect(updated.name, 'NewName');
expect(pet.name, 'Fluffy'); // Original unchanged
});
});
}Key points:
- Test all JSON field mappings (snake_case ↔ camelCase)
- Test optional/nullable fields
- Test immutability (copyWith creates new instance)
- Test serialization roundtrip
Controllers manage state transitions. Test them with mocked repositories:
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pet_dating_app/controllers/pet_controller.dart';
import 'package:pet_dating_app/models/pet_model.dart';
// Mock repository
class MockPetRepository extends Mock implements PetRepository {}
void main() {
group('PetNotifier', () {
test('initial state is empty', () {
final container = ProviderContainer();
final notifier = PetNotifier();
final state = notifier.build();
expect(state.myPets, isEmpty);
expect(state.isLoading, false);
});
test('loadPets updates state with fetched pets', () async {
final mockRepo = MockPetRepository();
final testPets = [
PetModel(...),
PetModel(...),
];
when(() => mockRepo.fetchMyPets('user-123'))
.thenAnswer((_) async => testPets);
final container = ProviderContainer(
overrides: [
petRepositoryProvider.overrideWithValue(mockRepo),
],
);
final notifier = container.read(petProvider.notifier);
await notifier.loadPets('user-123');
final state = container.read(petProvider);
expect(state.myPets.length, 2);
expect(state.isLoading, false);
});
test('loadPets handles errors gracefully', () async {
final mockRepo = MockPetRepository();
when(() => mockRepo.fetchMyPets('user-123'))
.thenThrow(Exception('Network error'));
final container = ProviderContainer(
overrides: [
petRepositoryProvider.overrideWithValue(mockRepo),
],
);
final notifier = container.read(petProvider.notifier);
await notifier.loadPets('user-123');
final state = container.read(petProvider);
expect(state.error, contains('Network error'));
expect(state.isLoading, false);
});
});
}Key points:
- Use
ProviderContainerfor testing Riverpod providers - Mock repositories with
MockTail - Test both success and error paths
- Verify state transitions
Tests run automatically on:
- Push to
main,develop, or feature branches - Pull requests to
mainordevelop
View results in GitHub Actions: https://github.com/CodeStorm-Hub/petsphere/actions
Workflow: .github/workflows/test-and-build.yml
| Component | Target | Status |
|---|---|---|
| Models | 100% | ✅ In progress |
| Controllers | 80% | ⏳ Next phase |
| Repositories | 70% | ⏳ Next phase |
| Overall | 70% | ✅ Started |
- Write tests alongside code, not after
- Test behavior, not implementation
- Use descriptive test names:
test('loadPets sets isLoading to true while fetching')nottest('load') - Group related tests with
group() - Mock external dependencies (repositories, Supabase, etc.)
- Test both success and error paths
- Keep tests independent (no test depends on another)
- Use Arrange-Act-Assert pattern
- Test private methods (if you need to, they should be public)
- Mock internal widgets (test the widget behavior, not implementation)
- Use
sleep()to wait for async operations (useasync/await) - Hardcode test data (use factories or fixtures)
- Ignore test failures ("flaky tests" point to real bugs)
- Write overly complex tests (if test is complex, production code is too)
flutter test -vflutter test test/models/pet_model_test.dart -vflutter test --timeout=120sSet breakpoints in test files and run with debugger:
- VS Code: F5 or Run → Start Debugging
- Android Studio: Run → Debug 'test'
For full app workflow testing:
flutter test integration_test/app_test.dartOr on specific device:
flutter drive --target=integration_test/app_test.dartMeasure test execution time:
flutter test --reporter json | jq '.tests[] | {name: .name, duration: .duration}'Target: Tests should complete in <5 minutes total.
- Add model tests for each new model
- Add controller tests for complex state logic
- Aim for 80%+ coverage on critical paths
- Use
goldenfiles for widget visual regression testing (future) - Set up performance regression detection (future)
Last Updated: May 5, 2026 Coverage: 0% → 10% (models baseline)