mogen_unit_tests
mogen_unit_tests scans your Flutter feature folders, parses Riverpod notifiers, and generates Mocktail-based unit test scaffolding for each discovered notifier.
It is designed for projects that keep their notifier and state files under lib/features/**/presentation/ and want a fast starting point for repeatable unit tests.
What it generates
For every notifier it finds, the package creates a ready-to-run test file with:
Mock<Repository>classes for every repository dependency found viaref.read(...)orref.watch(...)Fake<Type>classes andregisterFallbackValue(...)calls for complex parameter types- a
ProviderContainerwith repository overrides insetUp container.dispose()intearDown- a
build()test group with success and error cases - one test group per public notifier method, each with success and error cases
Project layout
The CLI expects this layout inside your Flutter app:
lib/
└── features/
└── cart/
└── presentation/
├── notifiers/
│ └── cart_notifier.dart
└── states/
└── cart_state.dart
Generated tests are written to:
test/
└── features/
└── unit/
└── features/
└── cart/
└── cart_notifier_test.dart
The generated path is
test/unit/features/<feature>/<notifier>_test.dart.
Installation
Add the package to your Flutter app's dev_dependencies:
dev_dependencies:
mogen_unit_tests:
Install it:
dart pub get
Usage
Run the generator from the root of your Flutter app:
dart run mogen_unit_tests
CLI options
| Flag | Short | Default | Description |
|---|---|---|---|
--root |
-r |
. |
Project root directory |
--dry-run |
-d |
false |
Preview generated files without writing them |
--verbose |
-v |
false |
Print progress details while scanning and generating |
--version |
Print the package version and exit | ||
--help |
-h |
Show the CLI help text |
Examples
# Generate tests from the current directory
dart run mogen_unit_tests
# Preview the output without writing files
dart run mogen_unit_tests --dry-run --verbose
# Run against a different Flutter project
dart run mogen_unit_tests --root /path/to/my_flutter_app
Example
Input notifier
@riverpod
class CartNotifier extends _$CartNotifier {
@override
Future<CartState> build() async {
final repo = ref.read(cartRepositoryProvider);
return repo.fetchCart();
}
Future<void> addItem(CartItem item) async {
final repo = ref.read(cartRepositoryProvider);
await repo.addItem(item);
ref.invalidateSelf();
}
Future<void> clearCart() async {
final repo = ref.read(cartRepositoryProvider);
await repo.clearCart();
ref.invalidateSelf();
}
}
Input state
class CartState {
final List<CartItem> items;
final double total;
const CartState({required this.items, required this.total});
}
Generated output
// GENERATED BY mogen_unit_tests — DO NOT EDIT
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:my_app/features/cart/presentation/notifiers/cart_notifier.dart';
import 'package:my_app/features/cart/presentation/states/cart_state.dart';
// TODO: import the file that declares CartRepository
class MockCartRepository extends Mock implements CartRepository {}
class FakeCartItem extends Fake implements CartItem {}
void main() {
group('CartNotifier', () {
late MockCartRepository mockCartRepository;
late ProviderContainer container;
setUpAll(() {
registerFallbackValue(FakeCartItem());
});
setUp(() {
mockCartRepository = MockCartRepository();
container = ProviderContainer(
overrides: [
cartRepositoryProvider.overrideWithValue(mockCartRepository),
],
);
});
tearDown(() {
container.dispose();
});
group('build', () {
test('returns CartState successfully', () async {
// when(() => mockCartRepository.fetchCart())
// .thenAnswer((_) async => CartState(items: [], total: 0));
final result = await container.read(cartNotifierProvider.future);
expect(result, isA<CartState>());
});
test('emits error when repository throws', () async {
// when(() => mockCartRepository.fetchCart())
// .thenThrow(Exception('test error'));
await container
.read(cartNotifierProvider.future)
.catchError((_) => FakeCartState());
expect(container.read(cartNotifierProvider), isA<AsyncError>());
});
});
});
}
What you still need to fill in
The generator scaffolds the repository stubs as comments so you can replace them with your real method names and return values.
// when(() => mockCartRepository.fetchCart())
// .thenAnswer((_) async => CartState(items: [], total: 0));
Everything else is generated for you.
Supported notifier types
| Class | Supported |
|---|---|
AsyncNotifier<T> |
✅ |
AutoDisposeAsyncNotifier<T> |
✅ |
Notifier<T> |
✅ |
AutoDisposeNotifier<T> |
✅ |
Repository dependencies are detected from ref.read(...) and ref.watch(...) calls inside the notifier implementation.
Limitations
- Generates unit tests only — no widget or integration tests
- Repository stubs are scaffolded as comments and must be filled in manually
- Only notifiers under
presentation/notifiers/are scanned familynotifiers are not supported yet
Dependencies
| Package | Purpose |
|---|---|
analyzer |
AST parsing of notifier source files |
dart_style |
Formatting generated Dart output |
args |
CLI flag parsing |
glob |
Discovering feature folders |
path |
Cross-platform path handling |
yaml |
Reading package name from pubspec.yaml |
Project health notes
- This package is a CLI tool, not a Flutter app. Running
dart run mogen_unit_testsfrom this repository itself will not produce output unless you point it at a consuming Flutter app withlib/features/.... - The current output destination is
test/unit/features/..., which is now reflected in the examples above.
Libraries
- mogen_unit_tests
- CLI tool that scans
lib/features/**/presentation/notifiers/, parses RiverpodAsyncNotifierclasses, detects repository dependencies viaref.read(), and generates Mocktail-based unit tests with success and error cases for every method.