Skip to content

Commit 8ee9abd

Browse files
jdickersrawlins
authored andcommitted
Add ability to wait for an invocation
1 parent 45938c4 commit 8ee9abd

File tree

4 files changed

+303
-1
lines changed

4 files changed

+303
-1
lines changed

pkgs/mockito/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ cat.eatFood("Fish");
150150
expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]);
151151
```
152152

153+
## Waiting for an interaction
154+
```dart
155+
//waiting for a call
156+
cat.eatFood("Fish");
157+
await untilCalled(cat.chew()); //completes when cat.chew() is called
158+
//waiting for a call that has already happened
159+
cat.eatFood("Fish");
160+
await untilCalled(cat.eatFood(any)); //will complete immediately
161+
```
162+
153163
## Resetting mocks
154164
```dart
155165
//clearing collected interactions

pkgs/mockito/lib/mockito.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ export 'src/mock.dart'
4242
clearInteractions,
4343
reset,
4444
resetMockitoState,
45-
logInvocations;
45+
logInvocations,
46+
untilCalled;

pkgs/mockito/lib/src/mock.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
// Warning: Do not import dart:mirrors in this library, as it's exported via
1616
// lib/mockito.dart, which is used for Dart AOT projects such as Flutter.
1717

18+
import 'dart:async';
19+
1820
import 'package:meta/meta.dart';
1921
import 'package:mockito/src/call_pair.dart';
2022
import 'package:mockito/src/invocation_matcher.dart';
2123
import 'package:test/test.dart';
2224

2325
bool _whenInProgress = false;
26+
bool _untilCalledInProgress = false;
2427
bool _verificationInProgress = false;
2528
_WhenCall _whenCall;
29+
_UntilCall _untilCall;
2630
final List<_VerifyCall> _verifyCalls = <_VerifyCall>[];
2731
final _TimeStampProvider _timer = new _TimeStampProvider();
2832
final List _capturedArgs = [];
@@ -78,6 +82,8 @@ class Mock {
7882

7983
static const _nullResponse = const CallPair.allInvocations(_answerNull);
8084

85+
final StreamController<Invocation> _invocationStreamController =
86+
new StreamController.broadcast();
8187
final _realCalls = <RealCall>[];
8288
final _responses = <CallPair>[];
8389

@@ -103,8 +109,12 @@ class Mock {
103109
} else if (_verificationInProgress) {
104110
_verifyCalls.add(new _VerifyCall(this, invocation));
105111
return null;
112+
} else if (_untilCalledInProgress) {
113+
_untilCall = new _UntilCall(this, invocation);
114+
return null;
106115
} else {
107116
_realCalls.add(new RealCall(this, invocation));
117+
_invocationStreamController.add(invocation);
108118
var cannedResponse = _responses.lastWhere(
109119
(cr) => cr.call.matches(invocation, {}),
110120
orElse: _defaultResponse);
@@ -470,6 +480,29 @@ class _WhenCall {
470480
}
471481
}
472482

483+
class _UntilCall {
484+
final InvocationMatcher _invocationMatcher;
485+
final Mock _mock;
486+
487+
_UntilCall(this._mock, Invocation invocation)
488+
: _invocationMatcher = new InvocationMatcher(invocation);
489+
490+
bool _matchesInvocation(RealCall realCall) =>
491+
_invocationMatcher.matches(realCall.invocation);
492+
493+
List<RealCall> get _realCalls => _mock._realCalls;
494+
495+
Future<Invocation> get invocationFuture {
496+
if (_realCalls.any(_matchesInvocation)) {
497+
return new Future.value(
498+
_realCalls.firstWhere(_matchesInvocation).invocation);
499+
}
500+
501+
return _mock._invocationStreamController.stream
502+
.firstWhere(_invocationMatcher.matches);
503+
}
504+
}
505+
473506
class _VerifyCall {
474507
final Mock mock;
475508
final Invocation verifyInvocation;
@@ -707,6 +740,29 @@ Expectation get when {
707740
};
708741
}
709742

743+
typedef Future<Invocation> InvocationLoader(_);
744+
745+
/// Returns a future [Invocation] that will complete upon the first occurrence
746+
/// of the given invocation.
747+
///
748+
/// Usage of this is as follows:
749+
///
750+
/// ```dart
751+
/// cat.eatFood("fish");
752+
/// await untilCalled(cat.chew());
753+
/// ```
754+
///
755+
/// In the above example, the untilCalled(cat.chew()) will complete only when
756+
/// that method is called. If the given invocation has already been called, the
757+
/// future will return immediately.
758+
InvocationLoader get untilCalled {
759+
_untilCalledInProgress = true;
760+
return (_) {
761+
_untilCalledInProgress = false;
762+
return _untilCall.invocationFuture;
763+
};
764+
}
765+
710766
/// Print all collected invocations of any mock methods of [mocks].
711767
void logInvocations(List<Mock> mocks) {
712768
List<RealCall> allInvocations =
@@ -728,8 +784,10 @@ void logInvocations(List<Mock> mocks) {
728784
/// or in `tearDown`.
729785
void resetMockitoState() {
730786
_whenInProgress = false;
787+
_untilCalledInProgress = false;
731788
_verificationInProgress = false;
732789
_whenCall = null;
790+
_untilCall = null;
733791
_verifyCalls.clear();
734792
_capturedArgs.clear();
735793
_typedArgs.clear();

pkgs/mockito/test/mockito_test.dart

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import 'dart:async';
16+
1517
import 'package:mockito/mockito.dart';
1618
import 'package:mockito/src/mock.dart'
1719
show resetMockitoState, throwOnMissingStub;
@@ -42,6 +44,33 @@ class RealClass {
4244
}
4345
}
4446

47+
class CallMethodsEvent {}
48+
49+
/// Listens on a stream and upon any event calls all methods in [RealClass].
50+
class RealClassController {
51+
final RealClass _realClass;
52+
53+
RealClassController(
54+
this._realClass, StreamController<CallMethodsEvent> streamController) {
55+
streamController.stream.listen(_callAllMethods);
56+
}
57+
58+
Future<Null> _callAllMethods(_) async {
59+
_realClass
60+
..methodWithoutArgs()
61+
..methodWithNormalArgs(1)
62+
..methodWithListArgs([1, 2])
63+
..methodWithPositionalArgs(1, 2)
64+
..methodWithNamedArgs(1, y: 2)
65+
..methodWithTwoNamedArgs(1, y: 2, z: 3)
66+
..methodWithObjArgs(new RealClass())
67+
..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8])
68+
..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8])
69+
..getter
70+
..setter = "A";
71+
}
72+
}
73+
4574
abstract class Foo {
4675
String bar();
4776
}
@@ -352,6 +381,210 @@ void main() {
352381
});
353382
});
354383

384+
group("untilCalled", () {
385+
StreamController<CallMethodsEvent> streamController =
386+
new StreamController.broadcast();
387+
388+
group("on methods already called", () {
389+
test("waits for method without args", () async {
390+
mock.methodWithoutArgs();
391+
392+
await untilCalled(mock.methodWithoutArgs());
393+
394+
verify(mock.methodWithoutArgs()).called(1);
395+
});
396+
397+
test("waits for method with normal args", () async {
398+
mock.methodWithNormalArgs(1);
399+
400+
await untilCalled(mock.methodWithNormalArgs(any));
401+
402+
verify(mock.methodWithNormalArgs(any)).called(1);
403+
});
404+
405+
test("waits for method with list args", () async {
406+
mock.methodWithListArgs([1]);
407+
408+
await untilCalled(mock.methodWithListArgs(any));
409+
410+
verify(mock.methodWithListArgs(any)).called(1);
411+
});
412+
413+
test("waits for method with positional args", () async {
414+
mock.methodWithPositionalArgs(1, 2);
415+
416+
await untilCalled(mock.methodWithPositionalArgs(any, any));
417+
418+
verify(mock.methodWithPositionalArgs(any, any)).called(1);
419+
});
420+
421+
test("waits for method with named args", () async {
422+
mock.methodWithNamedArgs(1, y: 2);
423+
424+
await untilCalled(mock.methodWithNamedArgs(any, y: any));
425+
426+
verify(mock.methodWithNamedArgs(any, y: any)).called(1);
427+
});
428+
429+
test("waits for method with two named args", () async {
430+
mock.methodWithTwoNamedArgs(1, y: 2, z: 3);
431+
432+
await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any));
433+
434+
verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1);
435+
});
436+
437+
test("waits for method with obj args", () async {
438+
mock.methodWithObjArgs(new RealClass());
439+
440+
await untilCalled(mock.methodWithObjArgs(any));
441+
442+
verify(mock.methodWithObjArgs(any)).called(1);
443+
});
444+
445+
test("waits for function with positional parameters", () async {
446+
mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]);
447+
448+
await untilCalled(mock.typeParameterizedFn(any, any, any, any));
449+
450+
verify(mock.typeParameterizedFn(any, any, any, any)).called(1);
451+
});
452+
453+
test("waits for function with named parameters", () async {
454+
mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]);
455+
456+
await untilCalled(
457+
mock.typeParameterizedNamedFn(any, any, y: any, z: any));
458+
459+
verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any))
460+
.called(1);
461+
});
462+
463+
test("waits for getter", () async {
464+
mock.getter;
465+
466+
await untilCalled(mock.getter);
467+
468+
verify(mock.getter).called(1);
469+
});
470+
471+
test("waits for setter", () async {
472+
mock.setter = "A";
473+
474+
await untilCalled(mock.setter = "A");
475+
476+
verify(mock.setter = "A").called(1);
477+
});
478+
});
479+
480+
group("on methods not yet called", () {
481+
setUp(() {
482+
new RealClassController(mock, streamController);
483+
});
484+
485+
test("waits for method without args", () async {
486+
streamController.add(new CallMethodsEvent());
487+
verifyNever(mock.methodWithoutArgs());
488+
489+
await untilCalled(mock.methodWithoutArgs());
490+
491+
verify(mock.methodWithoutArgs()).called(1);
492+
});
493+
494+
test("waits for method with normal args", () async {
495+
streamController.add(new CallMethodsEvent());
496+
verifyNever(mock.methodWithNormalArgs(any));
497+
498+
await untilCalled(mock.methodWithNormalArgs(any));
499+
500+
verify(mock.methodWithNormalArgs(any)).called(1);
501+
});
502+
503+
test("waits for method with list args", () async {
504+
streamController.add(new CallMethodsEvent());
505+
verifyNever(mock.methodWithListArgs(any));
506+
507+
await untilCalled(mock.methodWithListArgs(any));
508+
509+
verify(mock.methodWithListArgs(any)).called(1);
510+
});
511+
512+
test("waits for method with positional args", () async {
513+
streamController.add(new CallMethodsEvent());
514+
verifyNever(mock.methodWithPositionalArgs(any, any));
515+
516+
await untilCalled(mock.methodWithPositionalArgs(any, any));
517+
518+
verify(mock.methodWithPositionalArgs(any, any)).called(1);
519+
});
520+
521+
test("waits for method with named args", () async {
522+
streamController.add(new CallMethodsEvent());
523+
verifyNever(mock.methodWithNamedArgs(any, y: any));
524+
525+
await untilCalled(mock.methodWithNamedArgs(any, y: any));
526+
527+
verify(mock.methodWithNamedArgs(any, y: any)).called(1);
528+
});
529+
530+
test("waits for method with two named args", () async {
531+
streamController.add(new CallMethodsEvent());
532+
verifyNever(mock.methodWithTwoNamedArgs(any, y: any, z: any));
533+
534+
await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any));
535+
536+
verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1);
537+
});
538+
539+
test("waits for method with obj args", () async {
540+
streamController.add(new CallMethodsEvent());
541+
verifyNever(mock.methodWithObjArgs(any));
542+
543+
await untilCalled(mock.methodWithObjArgs(any));
544+
545+
verify(mock.methodWithObjArgs(any)).called(1);
546+
});
547+
548+
test("waits for function with positional parameters", () async {
549+
streamController.add(new CallMethodsEvent());
550+
verifyNever(mock.typeParameterizedFn(any, any, any, any));
551+
552+
await untilCalled(mock.typeParameterizedFn(any, any, any, any));
553+
554+
verify(mock.typeParameterizedFn(any, any, any, any)).called(1);
555+
});
556+
557+
test("waits for function with named parameters", () async {
558+
streamController.add(new CallMethodsEvent());
559+
verifyNever(mock.typeParameterizedNamedFn(any, any, y: any, z: any));
560+
561+
await untilCalled(
562+
mock.typeParameterizedNamedFn(any, any, y: any, z: any));
563+
564+
verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any))
565+
.called(1);
566+
});
567+
568+
test("waits for getter", () async {
569+
streamController.add(new CallMethodsEvent());
570+
verifyNever(mock.getter);
571+
572+
await untilCalled(mock.getter);
573+
574+
verify(mock.getter).called(1);
575+
});
576+
577+
test("waits for setter", () async {
578+
streamController.add(new CallMethodsEvent());
579+
verifyNever(mock.setter = "A");
580+
581+
await untilCalled(mock.setter = "A");
582+
583+
verify(mock.setter = "A").called(1);
584+
});
585+
});
586+
});
587+
355588
group("verify()", () {
356589
test("should verify method without args", () {
357590
mock.methodWithoutArgs();

0 commit comments

Comments
 (0)