Skip to main content

Overview

Zeus uses a comprehensive testing strategy including unit tests, widget tests, and integration tests.

Unit Tests

Test business logic in isolation:
import 'package:flutter_test/flutter_test.dart';
import 'package:zeus_app/cache/cache.dart';

void main() {
  group('CacheManager', () {
    late CacheManager cache;

    setUp(() async {
      cache = await CacheManager.initialize();
      cache.configureSync(StubSyncApiClient());
    });

    tearDown(() async {
      await cache.dispose();
    });

    test('creates wallet with correct properties', () async {
      final wallet = await cache.createWallet(
        name: 'Test Wallet',
        currency: 'EUR',
        initialBalance: 100.0,
      );

      expect(wallet.name, equals('Test Wallet'));
      expect(wallet.currency, equals('EUR'));
      expect(wallet.balance, equals(100.0));
      expect(wallet.syncStatus, equals(SyncStatus.pendingCreate));
    });

    test('soft delete marks wallet as deleted', () async {
      final wallet = await cache.createWallet(
        name: 'Delete Me',
        currency: 'USD',
      );

      await cache.wallets.delete(wallet.id);

      final deleted = await cache.wallets.findById(wallet.id);
      expect(deleted, isNull);
      
      // But still exists with isDeleted = true
      final allWithDeleted = await cache.wallets.findAll(includeDeleted: true);
      final found = allWithDeleted.firstWhere((w) => w.id == wallet.id);
      expect(found.isDeleted, isTrue);
      expect(found.syncStatus, equals(SyncStatus.pendingDelete));
    });
  });
}

Widget Tests

Test UI components:
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  group('WalletPage', () {
    testWidgets('displays list of wallets', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: BlocProvider(
            create: (_) => MockWalletBloc(),
            child: WalletPage(),
          ),
        ),
      );

      expect(find.text('Wallets'), findsOneWidget);
      expect(find.byType(ListView), findsOneWidget);
    });

    testWidgets('shows loading indicator', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: BlocProvider(
            create: (_) => MockWalletBloc(),
            child: WalletPage(),
          ),
        ),
      );

      await tester.pump(); // Initial build

      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });
  });
}

BLoC Tests

Use bloc_test package:
import 'package:bloc_test/bloc_test.dart';

void main() {
  group('WalletBloc', () {
    late CacheManager cache;

    setUp(() async {
      cache = await CacheManager.initialize();
    });

    blocTest<WalletBloc, WalletState>(
      'emits [Loading, Loaded] when LoadWallets is added',
      build: () => WalletBloc(cache),
      act: (bloc) => bloc.add(LoadWallets()),
      expect: () => [
        isA<WalletLoading>(),
        isA<WalletLoaded>(),
      ],
    );

    blocTest<WalletBloc, WalletState>(
      'emits [Loading, Error] when LoadWallets fails',
      build: () => WalletBloc(cache),
      act: (bloc) => bloc.add(LoadWallets()),
      expect: () => [
        isA<WalletLoading>(),
        isA<WalletError>(),
      ],
    );
  });
}

Integration Tests

Test complete user flows:
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:zeus_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('End-to-end test', () {
    testWidgets('create wallet and transaction', (tester) async {
      app.main();
      await tester.pumpAndSettle();

      // Tap add wallet button
      await tester.tap(find.byIcon(Icons.add));
      await tester.pumpAndSettle();

      // Enter wallet name
      await tester.enterText(
        find.byKey(Key('walletNameField')),
        'Test Wallet',
      );

      // Save
      await tester.tap(find.byKey(Key('saveButton')));
      await tester.pumpAndSettle();

      // Verify wallet appears
      expect(find.text('Test Wallet'), findsOneWidget);
    });
  });
}

Running Tests

# All tests
flutter test

# Specific file
flutter test test/cache/cache_manager_test.dart

# With coverage
flutter test --coverage

# Integration tests
flutter test integration_test/

# Specific device
flutter test -d <device-id>

Best Practices

  1. Test behavior, not implementation - Don’t test private methods
  2. Use mocks - Don’t hit real APIs in unit tests
  3. Test edge cases - Empty lists, null values, errors
  4. Keep tests fast - Unit tests should run in milliseconds
  5. Name tests clearly - test('should return error when network fails')