blob: 280ba54b3c1c2a2712434ec08227138356a6012b [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:async/async.dart';
import 'package:checks/checks.dart';
import 'package:checks/context.dart';
import 'package:test/scaffolding.dart';
import 'package:test_api/hooks.dart';
import '../test_shared.dart';
void main() {
group('FutureChecks', () {
group('completes', () {
test('succeeds for a future that completes to a value', () async {
await checkThat(_futureSuccess()).completes().which(equals(42));
});
test('rejects futures which complete as errors', () async {
await checkThat(_futureFail()).isRejectedByAsync(
it()..completes().which(equals(1)),
actual: ['a future that completes as an error'],
which: ['threw <UnimplementedError> at:', 'fake trace'],
);
});
test('can be described', () async {
await checkThat(it<Future<void>>()..completes())
.asyncDescription
.which(it()..deepEquals([' completes to a value']));
await checkThat(it<Future<int>>()..completes().which(equals(42)))
.asyncDescription
.which(it()
..deepEquals([
' completes to a value that:',
' equals <42>',
]));
});
});
group('throws', () {
test(
'succeeds for a future that compeletes to an error of the expected type',
() async {
await checkThat(_futureFail())
.throws<UnimplementedError>()
.which(it()..has((p0) => p0.message, 'message').isNull());
});
test('fails for futures that complete to a value', () async {
await checkThat(_futureSuccess()).isRejectedByAsync(
it()..throws(),
actual: ['completed to <42>'],
which: ['did not throw'],
);
});
test('failes for futures that complete to an error of the wrong type',
() async {
await checkThat(_futureFail()).isRejectedByAsync(
it()..throws<StateError>(),
actual: ['completed to error <UnimplementedError>'],
which: [
'threw an exception that is not a StateError at:',
'fake trace'
],
);
});
test('can be described', () async {
await checkThat(it<Future<void>>()..throws())
.asyncDescription
.which(it()..deepEquals([' completes to an error']));
await checkThat(it<Future<void>>()..throws<StateError>())
.asyncDescription
.which(it()
..deepEquals([' completes to an error of type StateError']));
});
});
group('doesNotComplete', () {
test('succeeds for a Future that never completes', () async {
checkThat(Completer<void>().future).doesNotComplete();
});
test('fails for a Future that completes as a value', () async {
Object? testFailure;
runZonedGuarded(() {
final completer = Completer<String>();
checkThat(completer.future).doesNotComplete();
completer.complete('value');
}, (e, st) {
testFailure = e;
});
await pumpEventQueue();
checkThat(testFailure)
.isA<TestFailure>()
.has((f) => f.message, 'message')
.isNotNull()
.equals('''
Expected: a Future<String> that:
does not complete
Actual: a future that completed to 'value\'''');
});
test('fails for a Future that completes as an error', () async {
Object? testFailure;
runZonedGuarded(() {
final completer = Completer<String>();
checkThat(completer.future).doesNotComplete();
completer.completeError('error', StackTrace.fromString('fake trace'));
}, (e, st) {
testFailure = e;
});
await pumpEventQueue();
checkThat(testFailure)
.isA<TestFailure>()
.has((f) => f.message, 'message')
.isNotNull()
.equals('''
Expected: a Future<String> that:
does not complete
Actual: a future that completed as an error:
Which: threw 'error'
fake trace''');
});
test('can be described', () async {
await checkThat(it<Future<void>>()..doesNotComplete())
.asyncDescription
.which(it()..deepEquals([' does not complete']));
});
});
});
group('StreamChecks', () {
group('emits', () {
test('succeeds for a stream that emits a value', () async {
await checkThat(_countingStream(5)).emits().which(equals(0));
});
test('fails for a stream that closes without emitting', () async {
await checkThat(_countingStream(0)).isRejectedByAsync(
it()..emits(),
actual: ['a stream'],
which: ['closed without emitting enough values'],
);
});
test('fails for a stream that emits an error', () async {
await checkThat(_countingStream(1, errorAt: 0)).isRejectedByAsync(
it()..emits(),
actual: ['a stream with error <UnimplementedError: Error at 1>'],
which: ['emitted an error instead of a value at:', 'fake trace'],
);
});
test('can be described', () async {
await checkThat(it<StreamQueue<void>>()..emits())
.asyncDescription
.which(it()..deepEquals([' emits a value']));
await checkThat(it<StreamQueue<int>>()..emits().which(equals(42)))
.asyncDescription
.which(it()
..deepEquals([
' emits a value that:',
' equals <42>',
]));
});
test('uses a transaction', () async {
final queue = _countingStream(1, errorAt: 0);
await softCheckAsync<StreamQueue<int>>(queue, it()..emits());
await checkThat(queue).emitsError();
});
});
group('emitsError', () {
test('succeeds for a stream that emits an error', () async {
await checkThat(_countingStream(1, errorAt: 0))
.emitsError<UnimplementedError>();
});
test('fails for a stream that closes without emitting an error',
() async {
await checkThat(_countingStream(0)).isRejectedByAsync(
it()..emitsError(),
actual: ['a stream'],
which: ['closed without emitting an expected error'],
);
});
test('fails for a stream that emits value', () async {
await checkThat(_countingStream(1)).isRejectedByAsync(
it()..emitsError(),
actual: ['a stream emitting value <0>'],
which: ['closed without emitting an error'],
);
});
test('fails for a stream that emits an error of the incorrect type',
() async {
await checkThat(_countingStream(1, errorAt: 0)).isRejectedByAsync(
it()..emitsError<StateError>(),
actual: ['a stream with error <UnimplementedError: Error at 1>'],
which: ['emitted an error which is not StateError at:', 'fake trace'],
);
});
test('can be described', () async {
await checkThat(it<StreamQueue<void>>()..emitsError())
.asyncDescription
.which(it()..deepEquals([' emits an error']));
await checkThat(it<StreamQueue<void>>()..emitsError<StateError>())
.asyncDescription
.which(it()..deepEquals([' emits an error of type StateError']));
await checkThat(it<StreamQueue<void>>()
..emitsError<StateError>()
.which(it()..has((e) => e.message, 'message').equals('foo')))
.asyncDescription
.which(it()
..deepEquals([
' emits an error of type StateError that:',
' has message that:',
' equals \'foo\''
]));
});
test('uses a transaction', () async {
final queue = _countingStream(1);
await softCheckAsync<StreamQueue<int>>(queue, it()..emitsError());
await checkThat(queue).emits().which((equals(0)));
});
});
group('emitsThrough', () {
test('succeeds for a stream that eventuall emits a matching value',
() async {
await checkThat(_countingStream(5)).emitsThrough(equals(4));
});
test('fails for a stream that closes without emitting a matching value',
() async {
await checkThat(_countingStream(4)).isRejectedByAsync(
it()..emitsThrough(equals(5)),
actual: ['a stream'],
which: ['ended after emitting 4 elements with none matching'],
);
});
test('can be described', () async {
await checkThat(it<StreamQueue<int>>()..emitsThrough(equals(42)))
.asyncDescription
.which(it()
..deepEquals([
' emits any values then emits a value that:',
' equals <42>'
]));
});
test('uses a transaction', () async {
final queue = _countingStream(1);
await softCheckAsync(
queue, it<StreamQueue<int>>()..emitsThrough(equals(42)));
checkThat(queue).emits().which(equals(0));
});
test('consumes events', () async {
final queue = _countingStream(3);
await checkThat(queue).emitsThrough(equals(1));
await checkThat(queue).emits().which((equals(2)));
});
});
group('emitsInOrder', () {
test('succeeds for happy case', () async {
await checkThat(_countingStream(2)).inOrder([
it()..emits().which(equals(0)),
it()..emits().which((equals(1))),
it()..isDone(),
]);
});
test('reports which condition failed', () async {
await checkThat(_countingStream(1)).isRejectedByAsync(
it()..inOrder([it()..emits(), it()..emits()]),
actual: ['a stream'],
which: [
'satisfied 1 conditions then',
'failed to satisfy the condition at index 1',
'because it closed without emitting enough values'
],
);
});
test('nestes the report for deep failures', () async {
await checkThat(_countingStream(2)).isRejectedByAsync(
it()..inOrder([it()..emits(), it()..emits().which(equals(2))]),
actual: ['a stream'],
which: [
'satisfied 1 conditions then',
'failed to satisfy the condition at index 1',
'because it:',
' emits a value that:',
' Actual: <1>',
' Which: are not equal',
],
);
});
test('gets described with the number of conditions', () async {
await checkThat(it<StreamQueue<int>>()..inOrder([it(), it()]))
.asyncDescription
.which(it()..deepEquals([' satisfies 2 conditions in order']));
});
test('uses a transaction', () async {
final queue = _countingStream(3);
await softCheckAsync<StreamQueue<int>>(
queue,
it()
..inOrder([
it()..emits().which(equals(0)),
it()..emits().which(equals(1)),
it()..emits().which(equals(42)),
]));
await checkThat(queue).inOrder([
it()..emits().which(equals(0)),
it()..emits().which(equals(1)),
it()..emits().which(equals(2)),
it()..isDone(),
]);
});
test('consumes events', () async {
final queue = _countingStream(3);
await checkThat(queue).inOrder([it()..emits(), it()..emits()]);
await checkThat(queue).emits().which(equals(2));
});
});
group('neverEmits', () {
test(
'succeeds for a stream that closes without emitting a matching value',
() async {
await checkThat(_countingStream(5)).neverEmits(equals(5));
});
test('fails for a stream that emits a matching value', () async {
await checkThat(_countingStream(6)).isRejectedByAsync(
it()..neverEmits(equals(5)),
actual: ['a stream'],
which: ['emitted <5>', 'following 5 other items'],
);
});
test('can be described', () async {
await checkThat(it<StreamQueue<int>>()..neverEmits(equals(42)))
.asyncDescription
.which(it()
..deepEquals([
' never emits a value that:',
' equals <42>',
]));
});
test('uses a transaction', () async {
final queue = _countingStream(2);
await softCheckAsync<StreamQueue<int>>(
queue, it()..neverEmits(equals(1)));
await checkThat(queue).inOrder([
it()..emits().which(equals(0)),
it()..emits().which(equals(1)),
it()..isDone(),
]);
});
});
group('mayEmit', () {
test('succeeds for a stream that emits a matching value', () async {
await checkThat(_countingStream(1)).mayEmit(equals(0));
});
test('succeeds for a stream that emits an error', () async {
await checkThat(_countingStream(1, errorAt: 0)).mayEmit(equals(0));
});
test('succeeds for a stream that closes', () async {
await checkThat(_countingStream(0)).mayEmit(equals(42));
});
test('consumes a matching event', () async {
final queue = _countingStream(2);
await softCheckAsync<StreamQueue<int>>(queue, it()..mayEmit(equals(0)));
await checkThat(queue).emits().which(equals(1));
});
test('does not consume a non-matching event', () async {
final queue = _countingStream(2);
await softCheckAsync<StreamQueue<int>>(queue, it()..mayEmit(equals(1)));
await checkThat(queue).emits().which(equals(0));
});
test('does not consume an error', () async {
final queue = _countingStream(1, errorAt: 0);
await softCheckAsync<StreamQueue<int>>(queue, it()..mayEmit(equals(0)));
await checkThat(queue)
.emitsError<UnimplementedError>()
.which(it()..has((e) => e.message, 'message').equals('Error at 1'));
});
});
group('mayEmitMultiple', () {
test('succeeds for a stream that emits a matching value', () async {
await checkThat(_countingStream(1)).mayEmitMultiple(equals(0));
});
test('succeeds for a stream that emits an error', () async {
await checkThat(_countingStream(1, errorAt: 0))
.mayEmitMultiple(equals(0));
});
test('succeeds for a stream that closes', () async {
await checkThat(_countingStream(0)).mayEmitMultiple(equals(42));
});
test('consumes matching events', () async {
final queue = _countingStream(3);
await softCheckAsync<StreamQueue<int>>(
queue, it()..mayEmitMultiple(it()..isLessThan(2)));
await checkThat(queue).emits().which(equals(2));
});
test('consumes no events if no events match', () async {
final queue = _countingStream(2);
await softCheckAsync<StreamQueue<int>>(
queue, it()..mayEmitMultiple(it()..isLessThan(0)));
await checkThat(queue).emits().which(equals(0));
});
test('does not consume an error', () async {
final queue = _countingStream(1, errorAt: 0);
await softCheckAsync<StreamQueue<int>>(
queue, it()..mayEmitMultiple(equals(0)));
await checkThat(queue)
.emitsError<UnimplementedError>()
.which(it()..has((e) => e.message, 'message').equals('Error at 1'));
});
});
group('isDone', () {
test('succeeds for an empty stream', () async {
await checkThat(_countingStream(0)).isDone();
});
test('fails for a stream that emits a value', () async {
await checkThat(_countingStream(1)).isRejectedByAsync(it()..isDone(),
actual: ['a stream'], which: ['emitted an unexpected value: <0>']);
});
test('fails for a stream that emits an error', () async {
final controller = StreamController<void>();
controller.addError('sad', StackTrace.fromString('fake trace'));
await checkThat(StreamQueue(controller.stream)).isRejectedByAsync(
it()..isDone(),
actual: ['a stream'],
which: ['emitted an unexpected error: \'sad\'', 'fake trace']);
});
test('uses a transaction', () async {
final queue = _countingStream(1);
await softCheckAsync<StreamQueue<int>>(queue, it()..isDone());
await checkThat(queue).emits().which(equals(0));
});
test('can be described', () async {
await checkThat(it<StreamQueue<int>>()..isDone())
.asyncDescription
.which(it()..deepEquals([' is done']));
});
});
group('emitsAnyOf', () {
test('succeeds for a stream that matches one condition', () async {
await checkThat(_countingStream(1)).anyOf([
it()..emits().which(equals(42)),
it()..emits().which((equals(0)))
]);
});
test('fails for a stream that matches no conditions', () async {
await checkThat(_countingStream(0)).isRejectedByAsync(
it()
..anyOf([
it()..emits(),
it()..emitsThrough(equals(1)),
]),
actual: [
'a stream'
],
which: [
'failed to satisfy any condition',
'failed the condition at index 0 because it:',
' closed without emitting enough values',
'failed the condition at index 1 because it:',
' ended after emitting 0 elements with none matching',
]);
});
test('includes nested details for nested failures', () async {
await checkThat(_countingStream(1)).isRejectedByAsync(
it()
..anyOf([
it()..emits().which(equals(42)),
it()..emitsThrough(equals(10)),
]),
actual: [
'a stream'
],
which: [
'failed to satisfy any condition',
'failed the condition at index 0 because it:',
' emits a value that:',
' Actual: <0>',
' Which: are not equal',
'failed the condition at index 1 because it:',
' ended after emitting 1 elements with none matching',
]);
});
test('gets described with the number of conditions', () async {
await checkThat(
it<StreamQueue<int>>()..anyOf([it()..emits(), it()..emits()]))
.asyncDescription
.which(it()..deepEquals([' satisfies any of 2 conditions']));
});
test('uses a transaction', () async {
final queue = _countingStream(1);
await softCheckAsync<StreamQueue<int>>(
queue,
it()
..anyOf([
it()..emits().which(equals(10)),
it()..emitsThrough(equals(42)),
]));
await checkThat(queue).emits().which(equals(0));
});
test('consumes events', () async {
final queue = _countingStream(3);
await checkThat(queue).anyOf(
[it()..emits().which(equals(1)), it()..emitsThrough(equals(1))]);
await checkThat(queue).emits().which(equals(2));
});
});
});
group('ChainAsync', () {
test('which', () async {
await checkThat(_futureSuccess()).completes().which(equals(42));
});
});
group('StreamQueueWrap', () {
test('can wrap streams in a queue', () async {
await checkThat(Stream.value(1)).withQueue.emits();
});
});
}
Future<int> _futureSuccess() => Future.microtask(() => 42);
Future<int> _futureFail() =>
Future.error(UnimplementedError(), StackTrace.fromString('fake trace'));
StreamQueue<int> _countingStream(int count, {int? errorAt}) => StreamQueue(
Stream.fromIterable(
Iterable<int>.generate(count, (index) {
if (index == errorAt) {
Error.throwWithStackTrace(UnimplementedError('Error at $count'),
StackTrace.fromString('fake trace'));
}
return index;
}),
),
);