From 1056895c31eee21678b7bf91f2417bc6d971a23f Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Tue, 21 Apr 2026 19:32:40 +0200 Subject: [PATCH] chore(legacy): add test infrastructure + mocktail --- modules/legacy/modules/account/pubspec.yaml | 1 + .../modules/account/test/canary_test.dart | 23 ++++ .../lib/src/testing/container_helper.dart | 6 + .../sf_shared/lib/src/testing/pump_app.dart | 35 +++++ packages/sf_shared/lib/testing.dart | 2 + packages/sf_shared/pubspec.yaml | 7 +- pubspec.lock | 8 ++ test/README.md | 121 ++++++++++++++++++ 8 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 modules/legacy/modules/account/test/canary_test.dart create mode 100644 packages/sf_shared/lib/src/testing/container_helper.dart create mode 100644 packages/sf_shared/lib/src/testing/pump_app.dart create mode 100644 packages/sf_shared/lib/testing.dart create mode 100644 test/README.md diff --git a/modules/legacy/modules/account/pubspec.yaml b/modules/legacy/modules/account/pubspec.yaml index fa9dd9d7..aa62242d 100644 --- a/modules/legacy/modules/account/pubspec.yaml +++ b/modules/legacy/modules/account/pubspec.yaml @@ -74,6 +74,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + mocktail: ^1.0.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/modules/legacy/modules/account/test/canary_test.dart b/modules/legacy/modules/account/test/canary_test.dart new file mode 100644 index 00000000..2a598270 --- /dev/null +++ b/modules/legacy/modules/account/test/canary_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sf_shared/testing.dart'; + +void main() { + testWidgets( + 'pumpApp renders child inside ProviderScope + MaterialApp', + (tester) async { + await pumpApp( + tester, + child: const Text('canary'), + ); + + expect(find.text('canary'), findsOneWidget); + }, + ); + + test('makeContainer creates a disposable ProviderContainer', () { + final container = makeContainer(); + addTearDown(container.dispose); + expect(container, isNotNull); + }); +} diff --git a/packages/sf_shared/lib/src/testing/container_helper.dart b/packages/sf_shared/lib/src/testing/container_helper.dart new file mode 100644 index 00000000..d8869bb6 --- /dev/null +++ b/packages/sf_shared/lib/src/testing/container_helper.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/misc.dart' show Override; + +ProviderContainer makeContainer({List overrides = const []}) { + return ProviderContainer(overrides: overrides); +} diff --git a/packages/sf_shared/lib/src/testing/pump_app.dart b/packages/sf_shared/lib/src/testing/pump_app.dart new file mode 100644 index 00000000..4215f5a2 --- /dev/null +++ b/packages/sf_shared/lib/src/testing/pump_app.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/misc.dart' show Override; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +/// Set [withLocalizations] only when the widget uses `context.translate(...)`; +/// it requires the JSON asset bundle loaded in the test environment. +Future pumpApp( + WidgetTester tester, { + required Widget child, + List overrides = const [], + Locale locale = const Locale('es'), + bool withLocalizations = false, +}) async { + final delegates = >[ + if (withLocalizations) SFLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ]; + + await tester.pumpWidget( + ProviderScope( + overrides: overrides, + child: MaterialApp( + locale: locale, + supportedLocales: supportedLanguages.map(Locale.new).toList(), + localizationsDelegates: delegates, + home: Scaffold(body: child), + ), + ), + ); +} diff --git a/packages/sf_shared/lib/testing.dart b/packages/sf_shared/lib/testing.dart new file mode 100644 index 00000000..67f3a677 --- /dev/null +++ b/packages/sf_shared/lib/testing.dart @@ -0,0 +1,2 @@ +export 'src/testing/container_helper.dart'; +export 'src/testing/pump_app.dart'; diff --git a/packages/sf_shared/pubspec.yaml b/packages/sf_shared/pubspec.yaml index 19b4115f..131c61cc 100644 --- a/packages/sf_shared/pubspec.yaml +++ b/packages/sf_shared/pubspec.yaml @@ -12,6 +12,10 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + flutter_test: + sdk: flutter sf_infrastructure: path: ../../packages/sf_infrastructure @@ -41,12 +45,11 @@ dependencies: permission_handler: ^12.0.1 dev_dependencies: - flutter_test: - sdk: flutter flutter_lints: ^5.0.0 riverpod_generator: ^3.0.3 build_runner: ^2.7.1 riverpod_lint: ^3.0.3 + mocktail: ^1.0.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/pubspec.lock b/pubspec.lock index b1d14bbb..a12ecbc4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1188,6 +1188,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa" + url: "https://pub.dev" + source: hosted + version: "1.0.5" mustache_template: dependency: transitive description: diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..2e252a4b --- /dev/null +++ b/test/README.md @@ -0,0 +1,121 @@ +# Tests — sf-app-platform + +Patrón estándar para los tests del monorepo. Cada package/module tiene su propia carpeta `test/`. + +## Setup mínimo + +Todo package que vaya a tener tests debe agregar: + +```yaml +# pubspec.yaml +dev_dependencies: + flutter_test: + sdk: flutter + mocktail: ^1.0.4 +``` + +Y si va a consumir los helpers compartidos: + +```yaml +dependencies: # puede ir en dev_dependencies si solo se usa en tests + sf_shared: + path: ../../packages/sf_shared # ajustar ruta relativa +``` + +## Helpers compartidos — `package:sf_shared/testing.dart` + +Se importan SOLO desde archivos `test/**`: + +```dart +import 'package:sf_shared/testing.dart'; +``` + +### `pumpApp(tester, child:, overrides:, locale:)` + +Pone el widget bajo test dentro de `ProviderScope` + `MaterialApp` + `SFLocalizations`. + +```dart +testWidgets('my screen shows title', (tester) async { + await pumpApp( + tester, + child: const MyScreen(), + overrides: [ + myRepositoryProvider.overrideWithValue(MockRepository()), + ], + ); + + expect(find.text('Title'), findsOneWidget); +}); +``` + +### `makeContainer(overrides:)` + +Para unit-testing de notifiers/providers sin widgets: + +```dart +test('controller emits AsyncError on failure', () async { + final container = makeContainer( + overrides: [ + myRepositoryProvider.overrideWithValue(ThrowingRepository()), + ], + ); + addTearDown(container.dispose); + + await container.read(myControllerProvider.notifier).submit(); + + expect(container.read(myControllerProvider).hasError, isTrue); +}); +``` + +## Mockeo de repositorios con mocktail + +```dart +class MockUsersRepository extends Mock implements UsersRepository {} + +void main() { + late MockUsersRepository mockRepo; + + setUp(() { + mockRepo = MockUsersRepository(); + }); + + test('loads users happy path', () async { + when(() => mockRepo.getUsers(userId: any(named: 'userId'))) + .thenAnswer((_) async => [const UserEntity(...)]); + + // ... + }); +} +``` + +## Correr tests + +```bash +# Todos los tests del package actual +flutter test + +# Un archivo específico +flutter test test/canary_test.dart + +# Con coverage +flutter test --coverage +``` + +Para correr tests de un package del monorepo: + +```bash +cd modules/legacy/modules/account && flutter test +``` + +## Convenciones + +- **1 archivo de test por archivo de producción** cuando sea posible (`foo_screen.dart` → `foo_screen_test.dart`). +- **Happy path + ≥1 error path** por feature migrada (ver `sf-legacy-migration-checklist.md`). +- **Golden tests** — no hay setup global todavía. Si una feature lo justifica, discutir antes. +- **Mocks no autogenerados** — usamos mocktail con `Mock implements XRepository` en lugar de build_runner para mocks. + +## Referencias + +- [mocktail docs](https://pub.dev/packages/mocktail) +- [flutter_test docs](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) +- `sf-legacy-migration-checklist.md` — plan de migración que consume estos helpers.