Mocking dependencies in Flutter tests
By Ann Tech · 28 February 2025
Tests that hit real APIs are slow, fragile, and make CI unreliable. Mocking dependencies isolates the unit under test so tests run fast and deterministically.
Mockito vs Mocktail
mockito requires code generation. mocktail uses Dart's noSuchMethod — no generation step.
dev_dependencies:
mocktail: ^1.0.3 # Recommended — no code gen needed
# OR
mockito: ^5.4.4 # With build_runner
build_runner: ^2.4.8
Creating mocks with Mocktail
import 'package:mocktail/mocktail.dart';
// Create a mock for any class or abstract interface
class MockProductRepository extends Mock implements ProductRepository {}
class MockDio extends Mock implements Dio {}
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
Stubbing return values
final repo = MockProductRepository();
// Stub a Future
when(() => repo.getProducts()).thenAnswer(
(_) async => [Product(id: '1', name: 'Test Product', price: 9.99)],
);
// Stub with specific arguments
when(() => repo.getProduct(id: '1')).thenAnswer(
(_) async => Product(id: '1', name: 'Test Product', price: 9.99),
);
// Stub a Stream
when(() => repo.productsStream).thenAnswer(
(_) => Stream.fromIterable([products]),
);
// Stub an error
when(() => repo.getProducts()).thenThrow(
ApiException(statusCode: 500, message: 'Server error'),
);
// Return different results on successive calls
var callCount = 0;
when(() => repo.getProducts()).thenAnswer((_) async {
callCount++;
if (callCount == 1) return [];
return products;
});
Verifying calls
// Verify a method was called
verify(() => repo.getProducts()).called(1);
// Verify with arguments
verify(() => repo.getProduct(id: '1')).called(1);
// Verify was never called
verifyNever(() => repo.deleteProduct(id: any(named: 'id')));
// Verify order
verifyInOrder([
() => repo.startTransaction(),
() => repo.createOrder(any()),
() => repo.commitTransaction(),
]);
Unit testing a BLoC with mocks
void main() {
late MockProductRepository repo;
late ProductsBloc bloc;
final tProducts = [Product(id: '1', name: 'Test', price: 9.99)];
setUp(() {
repo = MockProductRepository();
bloc = ProductsBloc(repo);
});
tearDown(() => bloc.close());
group('ProductsLoadRequested', () {
test('emits loading then loaded on success', () async {
when(() => repo.getProducts()).thenAnswer((_) async => tProducts);
expectLater(
bloc.stream,
emitsInOrder([isA<ProductsLoading>(), isA<ProductsLoaded>()]),
);
bloc.add(ProductsLoadRequested());
});
test('emits loading then error on failure', () async {
when(() => repo.getProducts()).thenThrow(Exception('Network error'));
expectLater(
bloc.stream,
emitsInOrder([isA<ProductsLoading>(), isA<ProductsError>()]),
);
bloc.add(ProductsLoadRequested());
});
});
}
Unit testing a Riverpod notifier with mocks
void main() {
late MockProductRepository repo;
setUp(() => repo = MockProductRepository());
test('loads products', () async {
when(() => repo.getProducts()).thenAnswer(
(_) async => [testProduct],
);
final container = ProviderContainer(
overrides: [
productRepositoryProvider.overrideWithValue(repo),
],
);
addTearDown(container.dispose);
final result = await container.read(productsProvider.future);
expect(result, [testProduct]);
verify(() => repo.getProducts()).called(1);
});
}
Argument matchers
// Match any value
when(() => repo.getProduct(id: any(named: 'id')))
.thenAnswer((_) async => testProduct);
// Match a specific type
when(() => dio.post(any<String>(), data: any(named: 'data')))
.thenAnswer((_) async => Response(data: {}, requestOptions: RequestOptions()));
// Custom matcher
when(() => repo.getProduct(id: 'specific-id'))
.thenAnswer((_) async => specificProduct);
Faking complex objects
For objects that are hard to mock (value classes, sealed classes):
class FakeCartRepository extends Fake implements CartRepository {
final _items = <CartItem>[];
@override
Future<List<CartItem>> getItems() async => List.from(_items);
@override
Future<void> addItem(CartItem item) async => _items.add(item);
@override
Future<void> clear() async => _items.clear();
}
Common pitfalls
Mocking concrete classes that have unmocked methods. If a method is called but not stubbed, mocktail throws MissingStubError. Stub every method that might be called, or use when(() => mock.method()).thenReturn(...) broadly.
Not calling registerFallbackValue for custom types. When passing custom objects as arguments to any(), you need to register a fallback: registerFallbackValue(FakeProduct()). Otherwise any() fails with a type error.
Verifying calls to void methods. void methods need when(() => mock.voidMethod()).thenReturn(null) before calling (in strict mode) or can be called directly. For void futures: when(() => mock.deleteProduct(id: any(named: 'id'))).thenAnswer((_) async {}).
Sign in to like, dislike, or report.