| name | dart-generate-test-mocks |
| description | Define and generate mock objects for external dependencies using `package:mockito` and the `build_runner` code generation lifecycle for unit testing classes. |
| metadata | {"platforms":"dart","languages":"dart","category":"testing"} |
Contents
Structuring Code for Testability
To write effective unit tests, structure your codebase using dependency injection. Isolate operations that interact with physical layers (like disk storage, external servers, and platform channels) so they can be replaced by mock objects at test time:
- Constructor Injection: Pass all service dependencies (e.g. HTTP clients, database helper clients) into class constructors rather than instantiating them directly within the class.
- Interface Segregation: Define clear, abstract base classes representing your service contracts. Mocks should ideally target these abstract contracts rather than concrete service implementations.
Managing Dev Dependencies
Configure your pubspec.yaml to specify the packages required for code generation and mock testing:
- Add target testing frameworks and generators under
dev_dependencies:
dart pub add dev:test dev:mockito dev:build_runner
- Import Mockito annotations inside your test files:
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
Generating Mock Files
Leverage package:mockito alongside build_runner to generate mock structures automatically:
Stubbing and Verification Best Practices
Write robust mock interactions by adhering to these guidelines:
- Future and Stream Stubbing: When stubbing methods that return a
Future or a Stream, always use .thenAnswer((_) async => value). Never use .thenReturn() for asynchronous return values, as this causes runtime cast errors.
- Invocation Tracking: Use the
verify() API to verify that specific methods were invoked. Call .called(number) to assert precise invocation counts.
- Unused Assertions: Use
verifyNever() or verifyNoMoreInteractions(mock) to guarantee that no unexpected actions occurred during the test lifecycle.
Workflow: Generating Mocks and Validating Tests
Follow this checklist to establish mock configurations:
Examples
Complete Mocked Test Suit (Mockito)
This example shows how to mock a remote API client to test a database synchronization service.
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
// Define an abstract contract for a user repository
abstract class UserRepository {
Future<String> getUserName(int id);
}
// System Under Test consuming the repository
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<String> fetchDisplayName(int id) async {
try {
final name = await repository.getUserName(id);
return 'User: $name';
} catch (_) {
return 'Unknown User';
}
}
}
// 1. Annotate to generate MockUserRepository
@GenerateNiceMocks([MockSpec<UserRepository>()])
import 'user_service_test.mocks.dart';
void main() {
group('UserService', () {
late MockUserRepository mockRepo;
late UserService userService;
setUp(() {
mockRepo = MockUserRepository();
userService = UserService(mockRepo);
});
test('returns formatted display name when repository succeeds', () async {
// 2. Arrange: Stub the async getUserName method
when(mockRepo.getUserName(42)).thenAnswer(
(_) async => 'Alice',
);
// 3. Act: Run target method
final displayName = await userService.fetchDisplayName(42);
// 4. Assert: Validate outcomes
expect(displayName, equals('User: Alice'));
// 5. Verify: Assert the repository method was invoked with correct arguments
verify(mockRepo.getUserName(42)).called(1);
});
test('returns fallback string when repository throws exception', () async {
// Arrange: Stub to throw error
when(mockRepo.getUserName(any)).thenThrow(
Exception('Connection Timeout'),
);
// Act
final displayName = await userService.fetchDisplayName(99);
// Assert
expect(displayName, equals('Unknown User'));
});
});
}