Testing Guide
Comprehensive guide to testing MiyoList application
๐ Testing Overview
MiyoList uses a comprehensive testing strategy covering:
- Unit Tests - Test individual functions and classes
- Widget Tests - Test UI components and interactions
- Integration Tests - Test complete user flows
- Manual Testing - End-to-end feature validation
๐งช Unit Testing
Setting Up Tests
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:miyolist/features/anime/data/models/anime.dart';
void main() {
group('Anime Model Tests', () {
test('fromJson creates valid Anime object', () {
// Arrange
final json = {
'id': 1,
'title': {'romaji': 'Test Anime'},
'status': 'FINISHED',
};
// Act
final anime = Anime.fromJson(json);
// Assert
expect(anime.id, 1);
expect(anime.title, 'Test Anime');
expect(anime.status, 'FINISHED');
});
test('toJson creates valid map', () {
// Arrange
final anime = Anime(
id: 1,
title: 'Test Anime',
status: 'FINISHED',
);
// Act
final json = anime.toJson();
// Assert
expect(json['id'], 1);
expect(json['title'], 'Test Anime');
});
});
} Testing Services
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@GenerateMocks([ApiClient])
void main() {
group('AnimeService Tests', () {
late MockApiClient mockApiClient;
late AnimeService animeService;
setUp(() {
mockApiClient = MockApiClient();
animeService = AnimeService(mockApiClient);
});
test('fetchAnimeList returns list on success', () async {
// Arrange
when(mockApiClient.query(any))
.thenAnswer((_) async => {'data': []});
// Act
final result = await animeService.fetchAnimeList(userId: '123');
// Assert
expect(result, isA<List<Anime>>());
verify(mockApiClient.query(any)).called(1);
});
});
} Running Unit Tests
# Run all unit tests
flutter test
# Run specific test file
flutter test test/features/anime/anime_test.dart
# Run with coverage
flutter test --coverage
# Generate coverage report
genhtml coverage/lcov.info -o coverage/html ๐จ Widget Testing
Testing Widgets
import 'package:flutter_test/flutter_test.dart';
import 'package:miyolist/features/anime/presentation/widgets/anime_card.dart';
void main() {
testWidgets('AnimeCard displays anime information', (tester) async {
// Arrange
final anime = Anime(
id: 1,
title: 'Test Anime',
coverImage: 'https://example.com/image.jpg',
);
// Act
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: AnimeCard(anime: anime),
),
),
);
// Assert
expect(find.text('Test Anime'), findsOneWidget);
expect(find.byType(Image), findsOneWidget);
});
testWidgets('AnimeCard handles tap', (tester) async {
var tapped = false;
final anime = Anime(id: 1, title: 'Test');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: AnimeCard(
anime: anime,
onTap: () => tapped = true,
),
),
),
);
await tester.tap(find.byType(AnimeCard));
expect(tapped, true);
});
} Testing with Riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
testWidgets('Test with provider overrides', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
animeListProvider.overrideWith((ref) => mockAnimeList),
],
child: const MaterialApp(home: AnimeListPage()),
),
);
await tester.pumpAndSettle();
expect(find.byType(AnimeCard), findsWidgets);
}); ๐ Integration Testing
Setting Up Integration Tests
# Create integration test directory
mkdir -p integration_test
# Create test file
touch integration_test/app_test.dart Writing Integration Tests
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:miyolist/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('App Flow Tests', () {
testWidgets('Complete authentication flow', (tester) async {
app.main();
await tester.pumpAndSettle();
// Find and tap sign in button
final signInButton = find.text('Sign In');
expect(signInButton, findsOneWidget);
await tester.tap(signInButton);
await tester.pumpAndSettle();
// Verify navigation to login page
expect(find.text('Continue with AniList'), findsOneWidget);
});
testWidgets('Navigate through main tabs', (tester) async {
app.main();
await tester.pumpAndSettle();
// Tap Anime tab
await tester.tap(find.text('Anime'));
await tester.pumpAndSettle();
expect(find.byType(AnimeListPage), findsOneWidget);
// Tap Manga tab
await tester.tap(find.text('Manga'));
await tester.pumpAndSettle();
expect(find.byType(MangaListPage), findsOneWidget);
});
});
} Running Integration Tests
# Run on connected device/emulator
flutter test integration_test
# Run on specific platform
flutter test integration_test -d windows
flutter test integration_test -d android ๐ค Manual Testing Checklist
Authentication Flow
- โ Sign in with AniList works
- โ OAuth redirect completes successfully
- โ User profile loads after authentication
- โ Sign out clears session
Core Features
- โ Anime/Manga lists load correctly
- โ Search returns accurate results
- โ Filtering and sorting work
- โ Edit entry updates sync to AniList
- โ Activity feed shows recent updates
- โ Statistics display accurately
Offline Mode
- โ App works without internet
- โ Cached data displays correctly
- โ Changes sync when back online
- โ Offline indicator shows status
Platform-Specific
- โ Windows: Window controls work, keyboard shortcuts
- โ Android: Touch gestures, back button behavior
- โ All platforms: UI scales correctly
๐ Test Coverage
Target coverage goals:
- Overall: 80%+
- Business logic: 90%+
- Data models: 95%+
- UI widgets: 70%+
Checking Coverage
# Generate coverage report
flutter test --coverage
# View HTML report
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html โจ Testing Best Practices
- Test Naming: Use descriptive names that explain what is being tested
- Arrange-Act-Assert: Structure tests with clear setup, execution, and verification
- One Assert Per Test: Keep tests focused on a single behavior
- Mock External Dependencies: Isolate code being tested
- Test Edge Cases: Cover error conditions and boundary values
- Keep Tests Fast: Tests should run quickly for rapid feedback
- Avoid Test Interdependence: Tests should be independent and runnable in any order
๐ CI/CD Integration
Tests run automatically on:
- Every push to GitHub
- Pull request creation/update
- Before merging to main
โ ๏ธ Important
All tests must pass before a PR can be merged. Fix any failing tests promptly.