Skip to content

Commit 6573157

Browse files
committed
Replace MockCommand framework-testing examples with practical pattern
- Show real DataService with command using get_it for dependencies - MockDataService implements DataService and overrides with MockCommand - DataManager listens to service command and maintains own state - Demonstrate both queued results (with loadData) and manual simulation - Full dependency injection pattern throughout - Remove 400+ lines of framework behavior testing bloat
1 parent 1ea921a commit 6573157

File tree

2 files changed

+129
-160
lines changed

2 files changed

+129
-160
lines changed

code_samples/test/command_it/mock_command_example_test.dart

Lines changed: 115 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,150 @@
11
import 'package:command_it/command_it.dart';
22
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:get_it/get_it.dart';
4+
5+
final getIt = GetIt.instance;
6+
7+
// Stub for example
8+
class Item {
9+
final String id;
10+
final String name;
11+
Item(this.id, this.name);
12+
}
13+
14+
class ApiClient {
15+
Future<List<Item>> search(String query) async {
16+
await Future.delayed(Duration(milliseconds: 100));
17+
return [Item('1', 'Result for $query')];
18+
}
19+
}
320

421
// #region example
5-
/// Example service that uses a command (for dependency injection in tests)
22+
/// Real service with actual command
623
class DataService {
7-
final Command<void, List<String>> loadCommand;
24+
late final loadCommand = Command.createAsync<String, List<Item>>(
25+
(query) => getIt<ApiClient>().search(query),
26+
initialValue: [],
27+
);
28+
}
829

9-
DataService({required this.loadCommand});
30+
/// Mock service for testing - overrides command with MockCommand
31+
class MockDataService implements DataService {
32+
@override
33+
late final loadCommand = _mockCommand;
1034

11-
void loadData() {
12-
loadCommand.run();
35+
final _mockCommand = MockCommand<String, List<Item>>(
36+
initialValue: [],
37+
);
38+
39+
// Control methods make tests readable and maintainable
40+
void simulateLoading() {
41+
_mockCommand.startExecution();
42+
}
43+
44+
void simulateSuccess(List<Item> data) {
45+
_mockCommand.endExecutionWithData(data);
46+
}
47+
48+
void simulateError(String message) {
49+
_mockCommand.endExecutionWithError(message);
1350
}
1451
}
1552

16-
void main() {
17-
group('MockCommand Examples', () {
18-
test('Queue results with CommandResult', () async {
19-
final mockLoadCommand = MockCommand<void, List<String>>(
20-
initialValue: [],
21-
);
22-
23-
// Queue results for the next execution using CommandResult
24-
mockLoadCommand.queueResultsForNextExecuteCall([
25-
CommandResult<void, List<String>>(
26-
null, ['Item 1', 'Item 2', 'Item 3'], null, false),
27-
]);
53+
// Code that depends on DataService
54+
class DataManager {
55+
DataManager() {
56+
// Listen to service command and update local state
57+
getIt<DataService>().loadCommand.isRunning.listen((running, _) {
58+
_isLoading = running;
59+
});
2860

29-
// Inject into service
30-
final service = DataService(loadCommand: mockLoadCommand);
61+
getIt<DataService>().loadCommand.listen((data, _) {
62+
_currentData = data;
63+
});
64+
}
3165

32-
// Trigger the command
33-
service.loadData();
66+
bool _isLoading = false;
67+
bool get isLoading => _isLoading;
3468

35-
// Verify the command was called
36-
expect(mockLoadCommand.executionCount, 1);
69+
List<Item> _currentData = [];
70+
List<Item> get currentData => _currentData;
3771

38-
// Verify the result
39-
expect(mockLoadCommand.value, ['Item 1', 'Item 2', 'Item 3']);
40-
});
72+
Future<void> loadData(String query) async {
73+
getIt<DataService>().loadCommand(query);
74+
}
75+
}
4176

42-
test('Manually control execution states', () {
43-
final mockCommand = MockCommand<void, String>(
44-
initialValue: '',
45-
);
77+
void main() {
78+
group('MockCommand Pattern', () {
79+
test('Test manager with mock service - success state', () async {
80+
final mockService = MockDataService();
81+
getIt.registerSingleton<DataService>(mockService);
4682

47-
// Initially not running
48-
expect(mockCommand.isRunning.value, false);
83+
final manager = DataManager();
4984

50-
// Start execution manually
51-
mockCommand.startExecution();
52-
expect(mockCommand.isRunning.value, true);
85+
final testData = [Item('1', 'Test Item')];
5386

54-
// Complete execution with data
55-
mockCommand.endExecutionWithData('loaded data');
56-
expect(mockCommand.isRunning.value, false);
57-
expect(mockCommand.value, 'loaded data');
58-
});
87+
// Queue result for the next execution
88+
mockService._mockCommand.queueResultsForNextExecuteCall([
89+
CommandResult<String, List<Item>>('test', testData, null, false),
90+
]);
5991

60-
test('Simulate error scenarios', () {
61-
final mockCommand = MockCommand<void, String>(
62-
initialValue: '',
63-
);
92+
// Execute the command through the manager
93+
await manager.loadData('test');
6494

65-
CommandError? capturedError;
66-
mockCommand.errors.listen((error, _) => capturedError = error);
95+
// Wait for listener to fire
96+
await Future.delayed(Duration.zero);
6797

68-
// Simulate error with String message (not Exception)
69-
mockCommand.startExecution();
70-
mockCommand.endExecutionWithError('Network error');
98+
// Verify success state
99+
expect(manager.isLoading, false);
100+
expect(manager.currentData, testData);
71101

72-
expect(capturedError?.error.toString(), contains('Network error'));
73-
expect(mockCommand.isRunning.value, false);
102+
// Cleanup
103+
await getIt.reset();
74104
});
75105

76-
test('Complete execution without data (void commands)', () {
77-
final mockCommand = MockCommand<void, void>(
78-
initialValue: null,
79-
noReturnValue: true,
80-
);
81-
82-
var executionCompleted = false;
83-
mockCommand.results.listen((result, _) {
84-
if (!result.isRunning && !result.hasError) {
85-
executionCompleted = true;
86-
}
106+
test('Test manager with mock service - error state', () async {
107+
final mockService = MockDataService();
108+
getIt.registerSingleton<DataService>(mockService);
109+
110+
final manager = DataManager();
111+
112+
CommandError? capturedError;
113+
mockService.loadCommand.errors.listen((error, _) {
114+
capturedError = error;
87115
});
88116

89-
mockCommand.startExecution();
90-
mockCommand.endExecutionNoData();
117+
// Simulate error without using loadData
118+
mockService.simulateError('Network error');
119+
120+
// Wait for listener to fire
121+
await Future.delayed(Duration.zero);
91122

92-
expect(executionCompleted, true);
93-
expect(mockCommand.isRunning.value, false);
123+
// Verify error state
124+
expect(manager.isLoading, false);
125+
expect(capturedError?.error.toString(), contains('Network error'));
126+
127+
// Cleanup
128+
await getIt.reset();
94129
});
95130

96-
test('Track execution count', () {
97-
final mockCommand = MockCommand<String, void>(
98-
initialValue: null,
99-
noReturnValue: true,
100-
);
131+
test('Real service works as expected', () async {
132+
// Register real dependencies
133+
getIt.registerSingleton<ApiClient>(ApiClient());
134+
getIt.registerSingleton<DataService>(DataService());
135+
136+
final manager = DataManager();
137+
138+
// Test with real service
139+
await manager.loadData('flutter');
101140

102-
expect(mockCommand.executionCount, 0);
141+
await Future.delayed(Duration(milliseconds: 150));
103142

104-
mockCommand.run('test1');
105-
expect(mockCommand.executionCount, 1);
106-
expect(mockCommand.lastPassedValueToExecute, 'test1');
143+
expect(manager.currentData.isNotEmpty, true);
144+
expect(manager.currentData.first.name, contains('flutter'));
107145

108-
mockCommand.run('test2');
109-
expect(mockCommand.executionCount, 2);
110-
expect(mockCommand.lastPassedValueToExecute, 'test2');
146+
// Cleanup
147+
await getIt.reset();
111148
});
112149
});
113150
}

docs/documentation/command_it/testing.md

Lines changed: 14 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -139,37 +139,9 @@ test('ErrorFilter routes errors correctly', () async {
139139

140140
## MockCommand
141141

142-
### Using MockCommand
142+
For testing code that depends on commands, use the built-in `MockCommand` class to create controlled test environments. The pattern below shows how to create a real manager with actual commands, then a mock version for testing.
143143

144-
For testing code that depends on commands, use the built-in `MockCommand` class instead of creating real commands:
145-
146-
```dart
147-
import 'package:command_it/command_it.dart';
148-
149-
test('Service uses command correctly', () async {
150-
// Create a mock command
151-
final mockLoadCommand = MockCommand<void, List<String>>(
152-
initialValue: [],
153-
);
154-
155-
// Queue results for the next execution
156-
mockLoadCommand.queueResultsForNextExecuteCall([
157-
CommandResult<void, List<String>>(null, ['Item 1', 'Item 2', 'Item 3'], null, false),
158-
]);
159-
160-
// Inject into service
161-
final service = DataService(loadCommand: mockLoadCommand);
162-
163-
// Trigger the command
164-
service.loadData();
165-
166-
// Verify the command was called
167-
expect(mockLoadCommand.executionCount, 1);
168-
169-
// Verify the result
170-
expect(mockLoadCommand.value, ['Item 1', 'Item 2', 'Item 3']);
171-
});
172-
```
144+
<<< @/../code_samples/test/command_it/mock_command_example_test.dart#example
173145

174146
**Key MockCommand methods:**
175147

@@ -180,62 +152,22 @@ test('Service uses command correctly', () async {
180152
- **`endExecutionWithError(String message)`** - Complete execution with an error
181153
- **`executionCount`** - Track how many times the command was executed
182154

183-
**Testing loading states:**
184-
185-
```dart
186-
test('UI shows loading indicator', () async {
187-
final mockCommand = MockCommand<void, String>(
188-
initialValue: '',
189-
);
190-
191-
final loadingStates = <bool>[];
192-
mockCommand.isRunning.listen((running, _) => loadingStates.add(running));
193-
194-
// Start execution manually
195-
mockCommand.startExecution();
196-
expect(mockCommand.isRunning.value, true);
197-
198-
// Complete execution
199-
mockCommand.endExecutionWithData('loaded data');
200-
expect(mockCommand.isRunning.value, false);
201-
202-
expect(loadingStates, [false, true, false]);
203-
});
204-
```
205-
206-
**Testing error scenarios:**
207-
208-
```dart
209-
test('UI shows error message', () {
210-
final mockCommand = MockCommand<void, String>(
211-
initialValue: '',
212-
);
213-
214-
CommandError? capturedError;
215-
mockCommand.errors.listen((error, _) => capturedError = error);
216-
217-
// Simulate error
218-
mockCommand.startExecution();
219-
mockCommand.endExecutionWithError('Network error');
220-
221-
expect(capturedError?.error.toString(), contains('Network error'));
222-
});
223-
```
224-
225-
**Benefits of MockCommand:**
155+
**This pattern demonstrates:**
226156

227-
- ✅ No async delays - tests run faster
228-
- ✅ Full control over execution state
229-
- ✅ Verify execution count
230-
- ✅ Queue multiple results for sequential calls
231-
- ✅ Test loading, success, and error states independently
232-
- ✅ No need for real business logic in tests
157+
<ul style="list-style: none; padding-left: 0;">
158+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Real service with actual command using get_it for dependencies</li>
159+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Mock service implements real service and overrides command with MockCommand</li>
160+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Control methods make test code readable and maintainable</li>
161+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Manager uses get_it to access service (full dependency injection)</li>
162+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Tests register mock service to control command behavior</li>
163+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ No async delays - tests run instantly</li>
164+
</ul>
233165

234166
**When to use MockCommand:**
235167

236-
- Testing widgets that observe commands
237-
- Testing services that coordinate multiple commands
238-
- Unit testing command-dependent code
168+
- Testing code that depends on commands without async delays
169+
- Testing loading, success, and error state handling
170+
- Unit testing services that coordinate commands
239171
- When you need precise control over command state transitions
240172

241173
## See Also

0 commit comments

Comments
 (0)