Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ typedef StartProcess = Future<Process> 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<TestEvent> dartTest({
List<String>? arguments,
String? workingDirectory,
Map<String, String>? 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.
///
Expand All @@ -31,19 +63,27 @@ Stream<TestEvent> flutterTest({
bool runInShell = false,
StartProcess startProcess = Process.start,
}) {
return _runTestProcess(
() => startProcess(
'flutter',
['test', ...?arguments, '--reporter=json'],
environment: environment,
workingDirectory: workingDirectory,
runInShell: runInShell,
),
);
}

Stream<TestEvent> _runTestProcess(
Future<Process> Function() processRunner,
) {
final controller = StreamController<TestEvent>();
late StreamSubscription testEventSubscription;
late StreamSubscription errorSubscription;
late Future<Process> processFuture;

Future<void> _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();
Expand Down
2 changes: 1 addition & 1 deletion lib/very_good_test_runner.dart
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,117 @@ class MockTestProcess extends Mock implements TestProcess {}
class MockProcess extends Mock implements Process {}

void main() {
group('dartTest', () {
late StreamController<List<int>> stdoutController;
late StreamController<List<int>> 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 = <TestEvent>[];
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 = <TestEvent>[];
final errors = <String>[];
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 = <TestEvent>[];
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<List<int>> stdoutController;
late StreamController<List<int>> stderrController;
Expand Down