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.