mogen_unit_tests 1.4.2
mogen_unit_tests: ^1.4.2 copied to clipboard
Scans Riverpod notifiers and flutter_bloc blocs/cubits, then auto-generates Mocktail and bloc_test unit tests.
// ignore_for_file: avoid_print
/// This file demonstrates how mogen_unit_tests works end-to-end.
///
/// Given the following feature structure in your Flutter project:
///
/// ```
/// lib/
/// └── features/
/// └── cart/
/// └── presentation/
/// ├── notifiers/
/// │ └── cart_notifier.dart
/// └── states/
/// └── cart_state.dart
/// ```
///
/// Running `dart run mogen_unit_tests` from your project root generates:
///
/// ```
/// test/
/// └── unit/
/// └── features/
/// └── cart/
/// └── cart_notifier_test.dart
/// ```
///
/// ---
///
/// ## Example input — cart_notifier.dart
///
/// ```dart
/// class CartNotifier extends AsyncNotifier<CartState> {
/// @override
/// Future<CartState> build() async {
/// return ref.read(cartRepositoryProvider).fetchCart();
/// }
///
/// Future<void> addItem(CartItem item) async {
/// await ref.read(cartRepositoryProvider).addItem(item);
/// state = AsyncData(state.requireValue.copyWith(success: 'added'));
/// }
///
/// Future<void> removeItem(String itemId) async {
/// await ref.read(cartRepositoryProvider).removeItem(itemId);
/// state = AsyncData(state.requireValue.copyWith(success: 'removed'));
/// }
/// }
/// ```
///
/// `CartRepository.fetchCart()` returns a `Future<CartEntity>`, `addItem`
/// takes a custom `CartItem` (so mocktail needs a fallback value registered
/// for `any()` to work), and `CartState` exposes
/// `isLoadingAction`/`error`/`success` fields — the generator recognizes all
/// of these conventions and reflects them below. `fetchCart()` is called by
/// `build()`, so it is stubbed once in `setUp` and the error tests re-stub
/// it with `thenThrow` after initialising the notifier.
///
/// ## Example output — cart_notifier_test.dart
///
/// ```dart
/// // GENERATED BY mogen_unit_tests — YOU CAN REMOVE THIS COMMENT
///
/// import 'package:flutter_riverpod/flutter_riverpod.dart';
/// import 'package:flutter_test/flutter_test.dart';
/// import 'package:mocktail/mocktail.dart';
/// import 'package:my_app/core/errors/app_exception.dart';
///
/// import 'package:my_app/features/cart/presentation/notifiers/cart_notifier.dart';
/// import 'package:my_app/features/cart/presentation/states/cart_state.dart';
/// import 'package:my_app/features/cart/domain/repositories/cart_repository.dart';
/// import 'package:my_app/features/cart/data/repositories/cart_repository_impl.dart';
/// import 'package:my_app/features/cart/domain/entities/cart_entity.dart';
/// import 'package:my_app/features/cart/domain/entities/cart_item.dart';
///
/// // ── Mocks ────────────────────────────────────────────────────
/// class MockCartRepository extends Mock implements CartRepository {}
///
/// void main() {
/// TestWidgetsFlutterBinding.ensureInitialized();
///
/// group('CartNotifier', () {
/// late MockCartRepository mockCartRepository;
/// late ProviderContainer container;
///
/// setUpAll(() {
/// // Mocktail needs a fallback instance registered for every
/// // non-nullable custom type used with an any()/captureAny() matcher.
/// registerFallbackValue(CartItem.empty());
/// });
///
/// setUp(() {
/// mockCartRepository = MockCartRepository();
///
/// container = ProviderContainer(
/// overrides: [
/// // Override the provider so the notifier gets mockCartRepository
/// cartRepositoryProvider.overrideWithValue(mockCartRepository),
/// ],
/// );
///
/// // Arrange: stub dependencies used by build() and calls shared
/// // by multiple methods — once here instead of in every test.
/// when(
/// () => mockCartRepository.fetchCart(),
/// ).thenAnswer((_) async => CartEntity.empty());
/// });
///
/// tearDown(() {
/// container.dispose();
/// });
///
/// // ── addItem() ──────────────────────────────────────
/// group('addItem', () {
/// test('addItem completes successfully', () async {
/// // Arrange: stub repositories
/// when(
/// () => mockCartRepository.addItem(any()),
/// ).thenAnswer((_) async => null);
///
/// // Arrange: inputs
/// final item = CartItem.empty();
///
/// // Ensure notifier is initialised
/// await container.read(cartNotifierProvider.future);
///
/// // Act
/// await container.read(cartNotifierProvider.notifier).addItem(item);
///
/// // Assert
/// final finalState = container.read(cartNotifierProvider);
/// expect(finalState.requireValue.isLoadingAction, isFalse);
/// expect(finalState.requireValue.error, isNull);
/// expect(finalState.requireValue.success, isNotNull);
/// });
///
/// test('addItem shows an error when the repository fails', () async {
/// // Arrange: inputs
/// final item = CartItem.empty();
///
/// // Ensure notifier is initialised
/// await container.read(cartNotifierProvider.future);
///
/// // Arrange: make every dependency call this method performs fail.
/// // Re-stubbing overrides the success stubs from setUp().
/// when(
/// () => mockCartRepository.addItem(any()),
/// ).thenThrow(AppException.test());
///
/// // Act
/// await container.read(cartNotifierProvider.notifier).addItem(item);
///
/// // Assert
/// final finalState = container.read(cartNotifierProvider);
/// expect(finalState.requireValue.isLoadingAction, isFalse);
/// expect(finalState.requireValue.error, isNotNull);
/// expect(finalState.requireValue.success, isNull);
/// });
/// });
///
/// // ── removeItem() ──────────────────────────────────────
/// group('removeItem', () {
/// test('removeItem completes successfully', () async {
/// // Arrange: stub repositories
/// when(
/// () => mockCartRepository.removeItem(any()),
/// ).thenAnswer((_) async => null);
///
/// // Arrange: inputs
/// const itemId = '';
///
/// // Ensure notifier is initialised
/// await container.read(cartNotifierProvider.future);
///
/// // Act
/// await container.read(cartNotifierProvider.notifier).removeItem(itemId);
///
/// // Assert
/// final finalState = container.read(cartNotifierProvider);
/// expect(finalState.requireValue.isLoadingAction, isFalse);
/// expect(finalState.requireValue.error, isNull);
/// expect(finalState.requireValue.success, isNotNull);
/// });
///
/// test('removeItem shows an error when the repository fails', () async {
/// // Arrange: inputs
/// const itemId = '';
///
/// // Ensure notifier is initialised
/// await container.read(cartNotifierProvider.future);
///
/// // Arrange: make every dependency call this method performs fail.
/// // Re-stubbing overrides the success stubs from setUp().
/// when(
/// () => mockCartRepository.removeItem(any()),
/// ).thenThrow(AppException.test());
///
/// // Act
/// await container.read(cartNotifierProvider.notifier).removeItem(itemId);
///
/// // Assert
/// final finalState = container.read(cartNotifierProvider);
/// expect(finalState.requireValue.isLoadingAction, isFalse);
/// expect(finalState.requireValue.error, isNotNull);
/// expect(finalState.requireValue.success, isNull);
/// });
/// });
/// });
/// }
/// ```
///
/// This assumes the consuming project defines `AppException.test()` at
/// `lib/core/errors/app_exception.dart`, and that `CartEntity`/`CartItem`
/// expose a `.empty()` factory constructor — see the README's "Conventions
/// this generator assumes" section.
library;
void main() {
print('Run `dart run mogen_unit_tests` from your Flutter project root.');
}