$npx -y skills add flutter/agent-plugins --skill dart-generate-test-mocksDefine and generate mock objects for external dependencies using package:mockito and build_runner. Use when unit testing classes that depend on complex external services like APIs or databases.
| 1 | # Testing and Mocking Dart Applications |
| 2 | |
| 3 | ## Contents |
| 4 | - [Structuring Code for Testability](#structuring-code-for-testability) |
| 5 | - [Managing Dependencies](#managing-dependencies) |
| 6 | - [Generating Mocks](#generating-mocks) |
| 7 | - [Implementing Unit Tests](#implementing-unit-tests) |
| 8 | - [Workflow: Creating and Running Mocked Tests](#workflow-creating-and-running-mocked-tests) |
| 9 | - [Examples](#examples) |
| 10 | |
| 11 | ## Structuring Code for Testability |
| 12 | Design Dart classes to support dependency injection. Isolate complex external dependencies (like API clients or databases) so they can be replaced with mock objects during testing. |
| 13 | |
| 14 | - Inject external services (e.g., `http.Client`) through class constructors. |
| 15 | - Represent URLs strictly as `Uri` objects using `Uri.parse(string)`. |
| 16 | - Utilize Dart's object-oriented features (classes, mixins) to define clear interfaces for external interactions. |
| 17 | |
| 18 | ## Managing Dependencies |
| 19 | Configure the `pubspec.yaml` file with the necessary testing and code generation packages. |
| 20 | |
| 21 | - Add runtime dependencies (e.g., `package:http`) using `dart pub add http`. |
| 22 | - Add testing dependencies using `dart pub add dev:test dev:mockito dev:build_runner`. |
| 23 | - Import HTTP libraries with a prefix to avoid namespace collisions: `import 'package:http/http.dart' as http;`. |
| 24 | |
| 25 | ## Generating Mocks |
| 26 | Use `package:mockito` and `build_runner` to automatically generate mock classes for fixed scenarios and behavior verification. |
| 27 | |
| 28 | - Always use the `@GenerateNiceMocks` annotation (preferable to `@GenerateMocks` to avoid missing stub exceptions). |
| 29 | - Place the annotation in the test file, passing a list of `MockSpec<Type>()` objects. |
| 30 | - Import the generated file using the `.mocks.dart` extension. |
| 31 | - Execute `build_runner` to generate the mock files: `dart run build_runner build`. |
| 32 | |
| 33 | ## Implementing Unit Tests |
| 34 | Isolate the system under test using the generated mock objects. Use `package:test` to structure the test suite. |
| 35 | |
| 36 | - **Stubbing:** Configure mock behavior before interacting with the system under test. |
| 37 | - Use `when(mock.method()).thenReturn(value)` for synchronous methods. |
| 38 | - **CRITICAL:** Always use `thenAnswer((_) async => value)` for methods returning a `Future` or `Stream`. Never use `thenReturn` for asynchronous returns. |
| 39 | - **Verification:** Assert that the system under test interacted with the mock object correctly. |
| 40 | - Use `verify(mock.method()).called(1)` to check exact invocation counts. |
| 41 | - Use argument matchers like `any`, `anyNamed`, or `captureAny` for flexible verification. |
| 42 | |
| 43 | ## Workflow: Creating and Running Mocked Tests |
| 44 | |
| 45 | Use the following checklist to implement and verify mocked unit tests. |
| 46 | |
| 47 | ### Task Progress |
| 48 | - [ ] 1. Identify the external dependency to mock (e.g., `http.Client`). |
| 49 | - [ ] 2. Inject the dependency into the target class constructor. |
| 50 | - [ ] 3. Create a test file (e.g., `target_test.dart`) and add `@GenerateNiceMocks([MockSpec<Dependency>()])`. |
| 51 | - [ ] 4. Add the `part` or `import` directive for the generated `.mocks.dart` file. |
| 52 | - [ ] 5. Run `dart run build_runner build` to generate the mock classes. |
| 53 | - [ ] 6. Write the test cases using `group()` and `test()`. |
| 54 | - [ ] 7. Stub required behaviors using `when()`. |
| 55 | - [ ] 8. Execute the target method. |
| 56 | - [ ] 9. Verify interactions using `verify()` and assert outcomes using `expect()`. |
| 57 | - [ ] 10. Run the test suite using `dart test`. |
| 58 | |
| 59 | ### Feedback Loop: Test Failures |
| 60 | If tests fail or `build_runner` encounters errors: |
| 61 | 1. **Run validator:** Execute `dart test` or `dart run build_runner build`. |
| 62 | 2. **Review errors:** Check for missing stubs, mismatched argument matchers, or syntax errors in the generated files. |
| 63 | 3. **Fix:** |
| 64 | - If a mock method throws an unexpected null error, ensure you used `@GenerateNiceMocks`. |
| 65 | - If an async stub throws an `ArgumentError`, change `thenReturn` to `thenAnswer`. |
| 66 | - If `build_runner` fails, ensure the `.mocks.dart` import matches the file name exactly. |
| 67 | 4. Repeat until all tests pass. |
| 68 | |
| 69 | ## Examples |
| 70 | |
| 71 | ### High-Fidelity Mocking and Testing Example |
| 72 | |
| 73 | **1. System Under Test (`lib/api_service.dart`)** |
| 74 | ```dart |
| 75 | import 'dart:convert'; |
| 76 | import 'package:http/http.dart' as http; |
| 77 | |
| 78 | class ApiService { |
| 79 | final http.Client client; |
| 80 | |
| 81 | ApiService(this.client); |
| 82 | |
| 83 | Future<String> fetchData(String urlString) async { |
| 84 | final uri = Uri.parse(urlString); |
| 85 | final response = await client.get(uri); |
| 86 | |
| 87 | if (response.statusCode == 200) { |
| 88 | return jsonDecode(response.body)['data']; |
| 89 | } else { |
| 90 | throw Exception('Failed to load data'); |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | **2. Test Implementation (`test/api_service_test.dart`)** |
| 97 | ```dart |
| 98 | import 'package:test/test.dart'; |
| 99 | import 'package:mockito/annotations.dart'; |
| 100 | import 'package:mockito/mockito.dart'; |
| 101 | import 'packa |