platform/packages/process/doc/testing.md
2024-12-30 06:35:33 -07:00

5 KiB

Testing Utilities

The Process package provides comprehensive testing utilities for process-dependent code.

Process Faking

Basic Faking

final factory = Factory();

// Fake specific commands
factory.fake({
  'ls': 'file1.txt\nfile2.txt',
  'cat file1.txt': 'Hello, World!',
  'grep pattern': (process) => 'Matched line',
});

// Run fake processes
final result = await factory.command('ls').run();
expect(result.output().trim(), equals('file1.txt\nfile2.txt'));

Preventing Real Processes

// Prevent any real process execution
factory.fake().preventStrayProcesses();

// This will throw an exception
await factory.command('real-command').run();

Dynamic Results

factory.fake({
  'random': (process) => 
      DateTime.now().millisecondsSinceEpoch.toString(),
  'conditional': (process) => 
      process.env['SUCCESS'] == 'true' ? 'success' : 'failure',
});

Process Descriptions

Basic Description

final description = FakeProcessDescription()
  ..withExitCode(0)
  ..replaceOutput('Test output')
  ..replaceErrorOutput('Test error');

factory.fake({
  'test-command': description,
});

Simulating Long-Running Processes

final description = FakeProcessDescription()
  ..withOutputSequence(['Step 1', 'Step 2', 'Step 3'])
  ..withDelay(Duration(milliseconds: 100))
  ..runsFor(duration: Duration(seconds: 1));

factory.fake({
  'long-task': description,
});

Simulating Process Failures

final description = FakeProcessDescription()
  ..withExitCode(1)
  ..replaceOutput('Operation failed')
  ..replaceErrorOutput('Error: Invalid input');

factory.fake({
  'failing-task': description,
});

Process Sequences

Basic Sequences

final sequence = FakeProcessSequence()
  ..then(FakeProcessResult(output: 'First'))
  ..then(FakeProcessResult(output: 'Second'))
  ..then(FakeProcessResult(output: 'Third'));

factory.fake({
  'sequential-task': sequence,
});

Alternating Success/Failure

final sequence = FakeProcessSequence.alternating(3);
while (sequence.hasMore) {
  final result = sequence.call() as FakeProcessResult;
  print('Success: ${result.successful()}');
}

Custom Sequences

final sequence = FakeProcessSequence.fromOutputs([
  'Starting...',
  'Processing...',
  'Complete!',
]);

factory.fake({
  'progress-task': sequence,
});

Testing Process Pools

test('executes processes concurrently', () async {
  factory.fake({
    'task1': FakeProcessDescription()
      ..withDelay(Duration(seconds: 1))
      ..replaceOutput('Result 1'),
    'task2': FakeProcessDescription()
      ..withDelay(Duration(seconds: 1))
      ..replaceOutput('Result 2'),
  });

  final results = await factory.pool((pool) {
    pool.command('task1');
    pool.command('task2');
  }).start();

  expect(results.successful(), isTrue);
  expect(results.total, equals(2));
});

Testing Process Pipes

test('pipes output between processes', () async {
  factory.fake({
    'generate': 'initial data',
    'transform': (process) => process.input.toUpperCase(),
    'filter': (process) => process.input.contains('DATA') ? process.input : '',
  });

  final result = await factory.pipeThrough((pipe) {
    pipe.command('generate');
    pipe.command('transform');
    pipe.command('filter');
  }).run();

  expect(result.output(), equals('INITIAL DATA'));
});

Best Practices

  1. Use preventStrayProcesses() in tests to catch unintended process execution
  2. Simulate realistic scenarios with delays and sequences
  3. Test both success and failure cases
  4. Test process configuration (environment, working directory, etc.)
  5. Test process coordination (pools and pipes)
  6. Use process descriptions for complex behaviors
  7. Test timeout and error handling
  8. Mock system-specific behaviors
  9. Clean up resources in tests
  10. Test real-time output handling

Example Test Suite

void main() {
  group('Process Manager', () {
    late Factory factory;

    setUp(() {
      factory = Factory();
      factory.fake().preventStrayProcesses();
    });

    test('handles successful process', () async {
      factory.fake({
        'successful-task': FakeProcessDescription()
          ..withExitCode(0)
          ..replaceOutput('Success!'),
      });

      final result = await factory
          .command('successful-task')
          .run();

      expect(result.successful(), isTrue);
      expect(result.output(), equals('Success!'));
    });

    test('handles process failure', () async {
      factory.fake({
        'failing-task': FakeProcessDescription()
          ..withExitCode(1)
          ..replaceErrorOutput('Failed!'),
      });

      final result = await factory
          .command('failing-task')
          .run();

      expect(result.failed(), isTrue);
      expect(result.errorOutput(), equals('Failed!'));
    });
  });
}

For more information, see: