diff --git a/README.md b/README.md index eb52c41..e88116d 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,15 @@ A test runner for Flutter and Dart created by Very Good Ventures. import 'package:very_good_test_runner/very_good_test_runner.dart'; void main() { + // Run `dart test` in `path/to/project`. + dartTest(workingDirectory: 'path/to/project').listen((TestEvent event) { + // React to `TestEvent` instances. + print(event); + }); + + // Run `flutter test` in `path/to/project`. flutterTest(workingDirectory: 'path/to/project').listen((TestEvent event) { // React to `TestEvent` instances. - // See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md#json-reporter-protocol print(event); }); } diff --git a/lib/src/flutter_test_runner.dart b/lib/src/very_good_test_runner.dart similarity index 73% rename from lib/src/flutter_test_runner.dart rename to lib/src/very_good_test_runner.dart index a2125b9..5e2e4d6 100644 --- a/lib/src/flutter_test_runner.dart +++ b/lib/src/very_good_test_runner.dart @@ -15,6 +15,38 @@ typedef StartProcess = Future Function( ProcessStartMode mode, }); +/// Runs `dart test` and returns a stream of [TestEvent] +/// reported by the process. +/// +/// ```dart +/// void main() { +/// // React to `TestEvent` instances. +/// dartTest().listen(print); +/// } +/// ``` +Stream dartTest({ + List? arguments, + String? workingDirectory, + Map? environment, + bool runInShell = false, + StartProcess startProcess = Process.start, +}) { + return _runTestProcess( + () => startProcess( + 'dart', + [ + 'test', + ...?arguments, + '--reporter=json', + '--chain-stack-traces', + ], + environment: environment, + workingDirectory: workingDirectory, + runInShell: runInShell, + ), + ); +} + /// Runs `flutter test` and returns a stream of [TestEvent] /// reported by the process. /// @@ -31,19 +63,27 @@ Stream flutterTest({ bool runInShell = false, StartProcess startProcess = Process.start, }) { + return _runTestProcess( + () => startProcess( + 'flutter', + ['test', ...?arguments, '--reporter=json'], + environment: environment, + workingDirectory: workingDirectory, + runInShell: runInShell, + ), + ); +} + +Stream _runTestProcess( + Future Function() processRunner, +) { final controller = StreamController(); late StreamSubscription testEventSubscription; late StreamSubscription errorSubscription; late Future processFuture; Future _onListen() async { - processFuture = startProcess( - 'flutter', - ['test', ...?arguments, '--reporter=json'], - environment: environment, - workingDirectory: workingDirectory, - runInShell: runInShell, - ); + processFuture = processRunner(); final process = await processFuture; final errors = process.stderr.map((e) => utf8.decode(e).trim()); final testEvents = process.stdout.mapToTestEvents(); diff --git a/lib/very_good_test_runner.dart b/lib/very_good_test_runner.dart index 566a447..8f36af3 100644 --- a/lib/very_good_test_runner.dart +++ b/lib/very_good_test_runner.dart @@ -1,5 +1,5 @@ /// A test runner for Flutter and Dart created by Very Good Ventures. library very_good_test_runner; -export 'src/flutter_test_runner.dart' show flutterTest; export 'src/models/models.dart'; +export 'src/very_good_test_runner.dart' show dartTest, flutterTest; diff --git a/test/src/flutter_test_runner_test.dart b/test/src/very_good_test_runner_test.dart similarity index 85% rename from test/src/flutter_test_runner_test.dart rename to test/src/very_good_test_runner_test.dart index 5a34ae9..114f92b 100644 --- a/test/src/flutter_test_runner_test.dart +++ b/test/src/very_good_test_runner_test.dart @@ -24,6 +24,117 @@ class MockTestProcess extends Mock implements TestProcess {} class MockProcess extends Mock implements Process {} void main() { + group('dartTest', () { + late StreamController> stdoutController; + late StreamController> stderrController; + late Process process; + late TestProcess testProcess; + + setUp(() { + stdoutController = StreamController(); + stderrController = StreamController(); + process = MockProcess(); + testProcess = MockTestProcess(); + when( + () => testProcess.start( + any(), + any(), + workingDirectory: any(named: 'workingDirectory'), + environment: any(named: 'environment'), + includeParentEnvironment: any(named: 'includeParentEnvironment'), + runInShell: any(named: 'runInShell'), + ), + ).thenAnswer((_) async => process); + when(() => process.stdout).thenAnswer((_) => stdoutController.stream); + when(() => process.stderr).thenAnswer((_) => stderrController.stream); + when(process.kill).thenReturn(true); + }); + + test('passes correct parameters to Process.start', () async { + final events = []; + const arguments = ['--no-pub']; + const environment = {'foo': 'bar'}; + const workingDirectory = './path/to/tests'; + const runInShell = true; + final testEvents = dartTest( + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + startProcess: testProcess.start, + runInShell: runInShell, + ); + final subscription = testEvents.listen(events.add); + await stdoutController.close(); + expect(events, isEmpty); + verify( + () => testProcess.start( + 'dart', + ['test', ...arguments, '--reporter=json', '--chain-stack-traces'], + workingDirectory: workingDirectory, + environment: environment, + runInShell: runInShell, + ), + ).called(1); + unawaited(subscription.cancel()); + }); + + test('emits error from stderr', () async { + const expectedError = 'oops'; + final events = []; + final errors = []; + final testEvents = dartTest(startProcess: testProcess.start); + final subscription = testEvents.listen(events.add, onError: errors.add); + stderrController.add(utf8.encode(expectedError)); + await stdoutController.close(); + expect(events, isEmpty); + expect(errors, equals([expectedError])); + unawaited(subscription.cancel()); + }); + + test('kills process when subscription is canceled', () async { + final events = []; + final testEvents = dartTest(startProcess: testProcess.start); + final subscription = testEvents.listen(events.add); + await subscription.cancel(); + verify(process.kill).called(1); + }); + + test('emits correctly (e2e)', () async { + final tempDirectory = Directory.systemTemp.createTempSync(); + File('${tempDirectory.path}/pubspec.yaml').writeAsStringSync( + ''' +name: example +version: 0.1.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dev_dependencies: + test: any +''', + ); + final testDirectory = Directory('${tempDirectory.path}/test') + ..createSync(); + File('${testDirectory.path}/example_test.dart').writeAsStringSync( + ''' +import 'package:test/test.dart'; + +void main() { + test('example', () { + expect(true, isTrue); + }); +} +''', + ); + expect( + dartTest(workingDirectory: tempDirectory.path) + .where((e) => e is DoneTestEvent) + .first, + completes, + ); + }); + }); + group('flutterTest', () { late StreamController> stdoutController; late StreamController> stderrController;