Mocking Dependencies with Mockito and Mocktail
Mocking Dependencies with Mockito and Mocktail
A mock is a test double that replaces a real dependency with a controlled substitute. Instead of making real HTTP calls, writing to a real database, or reading from the filesystem, your unit test interacts with a mock that you fully control. This is the foundation of isolated unit testing: the class under test never touches the outside world.
Dart's two dominant mocking libraries are Mockito (code-generation-based, official Google library) and Mocktail (no code generation required, drop-in alternative). Both let you stub return values and verify interactions, but they differ in setup cost and API style.
Why Mocking Matters
Consider a WeatherService that calls a remote API. A unit test for the widget or view-model that depends on it should not make a real network request because:
- Network calls are slow and flaky — they can fail even if your code is correct.
- You cannot control what the server returns, making assertions unreliable.
- Running thousands of tests in CI would hammer an external API.
- Some code paths (e.g., a 503 error response) are hard to reproduce with a real server.
A mock lets you inject a fake WeatherService that returns exactly the data — or throws exactly the exception — you need for each test scenario.
Mockito: Code-Generation Mocks
Add the dependencies to pubspec.yaml:
dev_dependencies:
mockito: ^5.4.0
build_runner: ^2.4.0
flutter_test:
sdk: flutter
Annotate your test file with @GenerateMocks, then run dart run build_runner build once to generate the .mocks.dart file. After that, use the generated class in your tests.
// weather_repository.dart
abstract class WeatherRepository {
Future<String> fetchCondition(String city);
}
// weather_cubit_test.dart
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'weather_cubit_test.mocks.dart'; // generated file
@GenerateMocks([WeatherRepository])
void main() {
late MockWeatherRepository mockRepo;
setUp(() {
mockRepo = MockWeatherRepository();
});
test('returns condition from repository', () async {
// Arrange — stub the method
when(mockRepo.fetchCondition('London'))
.thenAnswer((_) async => 'Cloudy');
// Act
final result = await mockRepo.fetchCondition('London');
// Assert
expect(result, equals('Cloudy'));
verify(mockRepo.fetchCondition('London')).called(1);
});
test('throws when repository throws', () async {
when(mockRepo.fetchCondition(any))
.thenThrow(Exception('Network error'));
expect(
() => mockRepo.fetchCondition('Paris'),
throwsA(isA<Exception>()),
);
});
}
dart run build_runner build --delete-conflicting-outputs every time you add a new class to @GenerateMocks. The generated .mocks.dart file should be committed to version control so CI does not need to run build_runner.Mocktail: No Code Generation
Mocktail is a popular alternative that removes the build_runner step entirely. You create a mock by extending Mock and implementing the interface — one line per class.
// pubspec.yaml dev_dependencies:
// mocktail: ^1.0.0
import 'package:mocktail/mocktail.dart';
import 'package:flutter_test/flutter_test.dart';
// One-line mock declaration — no annotations needed
class MockWeatherRepository extends Mock implements WeatherRepository {}
void main() {
late MockWeatherRepository mockRepo;
setUp(() {
mockRepo = MockWeatherRepository();
// Register fallback values for types used with any()
registerFallbackValue('');
});
test('returns stubbed condition', () async {
when(() => mockRepo.fetchCondition('Tokyo'))
.thenAnswer((_) async => 'Sunny');
final result = await mockRepo.fetchCondition('Tokyo');
expect(result, equals('Sunny'));
verify(() => mockRepo.fetchCondition('Tokyo')).called(1);
});
test('verifies method was never called', () async {
verifyNever(() => mockRepo.fetchCondition(any()));
});
}
when and verify — when(() => mock.method()) — not the direct call style of Mockito. Mixing the two syntaxes in the same project is a common source of confusion. Stick to one library per project.Stubbing Return Values
Both libraries support the same fundamental stub operations:
thenReturn(value)— returns a plain (non-Future) value synchronously.thenAnswer((_) async => value)— returns aFutureor uses the invocation arguments.thenThrow(exception)— throws an exception when the method is called.thenAnswer((_) async => throw exception)— throws asynchronously from aFuture.
Verifying Interactions
After the Act phase of a test you can assert not only what was returned but how the mock was called:
verify(mock.method(arg)).called(n)— asserts the method was called exactly n times.verifyNever(mock.method())— asserts the method was never called.verifyInOrder([...])(Mockito) — asserts calls happened in a specific sequence.verifyNoMoreInteractions(mock)— asserts no further unexpected calls were made.
Summary
Mocking is indispensable for fast, reliable unit tests. Mockito requires a one-time code-generation step but provides strong type safety. Mocktail eliminates that step with a zero-annotation API at the cost of slightly less compile-time safety. Both follow the Arrange–Act–Assert pattern: stub the dependencies in Arrange, exercise the code under test in Act, and assert both return values and mock interactions in Assert.
build_runner. Either way, only mock collaborators you own — avoid mocking third-party classes directly.