@@ -238,280 +238,6 @@ test('UI shows error message', () {
238238- Unit testing command-dependent code
239239- When you need precise control over command state transitions
240240
241- ## Testing with fake_async
242-
243- For precise timing control, use ` fake_async ` :
244-
245- ``` dart
246- import 'package:fake_async/fake_async.dart';
247-
248- test('Test with controlled time', () {
249- fakeAsync((async) {
250- var result = '';
251-
252- final command = Command.createAsyncNoParam<String>(
253- () async {
254- await Future.delayed(Duration(seconds: 5));
255- return 'delayed result';
256- },
257- initialValue: '',
258- );
259-
260- command.listen((value, _) => result = value);
261- command.run();
262-
263- // Immediately after run, still initial
264- expect(result, '');
265-
266- // Advance time
267- async.elapse(Duration(seconds: 5));
268-
269- // Now the result is set
270- expect(result, 'delayed result');
271- });
272- });
273- ```
274-
275- ## Testing Disposal
276-
277- Verify commands clean up properly:
278-
279- ``` dart
280- test('Command disposes correctly', () async {
281- var disposed = false;
282-
283- final command = Command.createAsyncNoParam<String>(
284- () async => 'result',
285- initialValue: '',
286- );
287-
288- // Add listener
289- command.listen((_, __) {});
290-
291- // Dispose
292- await command.dispose();
293-
294- // Verify disposed (accessing properties should throw)
295- expect(() => command.value, throwsA(anything));
296- });
297- ```
298-
299- ## Integration Testing
300-
301- ### Testing Commands in Managers
302-
303- ``` dart
304- class DataManager {
305- final api = ApiClient();
306-
307- late final loadCommand = Command.createAsyncNoParam<List<String>>(
308- () => api.fetchData(),
309- initialValue: [],
310- );
311-
312- void dispose() {
313- loadCommand.dispose();
314- }
315- }
316-
317- test('DataManager integration', () async {
318- final manager = DataManager();
319-
320- final result = await manager.loadCommand.runAsync();
321-
322- expect(result, isNotEmpty);
323-
324- manager.dispose();
325- });
326- ```
327-
328- ### Testing Command Chains
329-
330- ``` dart
331- test('Commands chain via restrictions', () async {
332- final loadCommand = Command.createAsyncNoParam<void>(
333- () async {
334- await Future.delayed(Duration(milliseconds: 50));
335- },
336- );
337-
338- final saveCommand = Command.createAsyncNoParam<void>(
339- () async {},
340- restriction: loadCommand.isRunningSync,
341- );
342-
343- loadCommand.run();
344-
345- // Save is restricted while load is running
346- expect(saveCommand.canRun.value, false);
347-
348- await Future.delayed(Duration(milliseconds: 100));
349-
350- // After load completes, save can run
351- expect(saveCommand.canRun.value, true);
352- });
353- ```
354-
355- ## Common Testing Patterns
356-
357- ### Pattern 1: Setup/Teardown
358-
359- ``` dart
360- group('Command Tests', () {
361- late Command<void, String> command;
362- late Collector<String> collector;
363-
364- setUp(() {
365- collector = Collector<String>();
366- command = Command.createAsyncNoParam<String>(
367- () async => 'result',
368- initialValue: '',
369- );
370- command.listen((value, _) => collector(value));
371- });
372-
373- tearDown(() async {
374- await command.dispose();
375- collector.reset();
376- });
377-
378- test('test 1', () async {
379- // Test using command and collector
380- });
381-
382- test('test 2', () async {
383- // Test using command and collector
384- });
385- });
386- ```
387-
388- ### Pattern 2: Verify All States
389-
390- ``` dart
391- test('Verify complete state flow', () async {
392- final states = <String>[];
393-
394- final command = Command.createAsyncNoParam<String>(
395- () async {
396- await Future.delayed(Duration(milliseconds: 50));
397- return 'done';
398- },
399- initialValue: 'initial',
400- );
401-
402- command.results.listen((result, _) {
403- if (result.isRunning) {
404- states.add('running');
405- } else if (result.hasError) {
406- states.add('error');
407- } else if (result.hasData) {
408- states.add('success');
409- }
410- });
411-
412- await command.runAsync();
413-
414- expect(states, ['success', 'running', 'success']);
415- // success (initial), running, success (completed)
416- });
417- ```
418-
419- ### Pattern 3: Error Recovery
420-
421- ``` dart
422- test('Command recovers after error', () async {
423- var shouldFail = true;
424-
425- final command = Command.createAsyncNoParam<String>(
426- () async {
427- if (shouldFail) {
428- throw Exception('Error');
429- }
430- return 'success';
431- },
432- initialValue: '',
433- );
434-
435- // First call fails
436- expect(() => command.runAsync(), throwsA(anything));
437-
438- await Future.delayed(Duration(milliseconds: 50));
439-
440- // Second call succeeds
441- shouldFail = false;
442- final result = await command.runAsync();
443- expect(result, 'success');
444- });
445- ```
446-
447- ## Debugging Tests
448-
449- ### Enable Print Statements
450-
451- ``` dart
452- void setupCollectors(Command command, {bool enablePrint = true}) {
453- command.canRun.listen((canRun, _) {
454- if (enablePrint) print('canRun: $canRun');
455- });
456-
457- command.results.listen((result, _) {
458- if (enablePrint) {
459- print('Result: data=${result.data}, error=${result.error}, '
460- 'isRunning=${result.isRunning}');
461- }
462- });
463- }
464- ```
465-
466- ### Use testWidgets for UI Integration
467-
468- ``` dart
469- testWidgets('CommandBuilder widget test', (tester) async {
470- final command = Command.createAsyncNoParam<String>(
471- () async => 'result',
472- initialValue: '',
473- );
474-
475- await tester.pumpWidget(
476- MaterialApp(
477- home: CommandBuilder(
478- command: command,
479- whileRunning: (context, _, __) => CircularProgressIndicator(),
480- onData: (context, data, _) => Text(data),
481- ),
482- ),
483- );
484-
485- await tester.pumpAndSettle();
486-
487- expect(find.text('result'), findsOneWidget);
488- });
489- ```
490-
491- ## Best Practices
492-
493- ** ✅ Do:**
494-
495- <ul style =" list-style : none ; padding-left : 0 ;" >
496- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Use <code >Collector</code > pattern for state verification</li >
497- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Test both success and error paths</li >
498- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Verify state transitions with <code >CommandResult</code ></li >
499- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Use <code >runAsync()</code > to await results in tests</li >
500- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Mock external dependencies</li >
501- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Test restriction behavior</li >
502- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >✅ Verify disposal</li >
503- </ul >
504-
505- ** ❌️ Don't:**
506-
507- <ul style =" list-style : none ; padding-left : 0 ;" >
508- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >❌️ Access <code >isRunning</code > on sync commands</li >
509- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >❌️ Forget to dispose commands in tearDown</li >
510- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >❌️ Test UI and business logic together</li >
511- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >❌️ Rely on timing without <code >fake_async</code ></li >
512- <li style =" padding-left : 1.5em ; text-indent : -1.5em ;" >❌️ Ignore error handling tests</li >
513- </ul >
514-
515241## See Also
516242
517243- [ Command Basics] ( /documentation/command_it/command_basics ) — Creating commands
0 commit comments