Use new matcher (#52)
* Start using the new InvocationMatcher
* Some fixes.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5a6633..972d940 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 2.0.2
+
+* Start using the new `InvocationMatcher` instead of the old matcher.
+* Change `throwOnMissingStub` back to invoking `Object.noSuchMethod`:
+ * It was never documented what the thrown type should be expected as
+ * You can now just rely on `throwsNoSuchMethodError` if you want to catch it
+
## 2.0.1
* Add a new `throwOnMissingStub` method to the API.
diff --git a/lib/src/call_pair.dart b/lib/src/call_pair.dart
new file mode 100644
index 0000000..bae33eb
--- /dev/null
+++ b/lib/src/call_pair.dart
@@ -0,0 +1,36 @@
+// Copyright 2017 Dart Mockito authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:matcher/matcher.dart';
+
+/// Returns a value dependent on the details of an [invocation].
+typedef T Answer<T>(Invocation invocation);
+
+/// A captured method or property accessor -> a function that returns a value.
+class CallPair<T> {
+ /// A captured method or property accessor.
+ final Matcher call;
+
+ /// Result function that should be invoked.
+ final Answer<T> response;
+
+ // TODO: Rename to `Expectation` in 3.0.0.
+ const CallPair(this.call, this.response);
+
+ const CallPair.allInvocations(this.response)
+ : call = const isInstanceOf<Invocation>();
+
+ @override
+ String toString() => '$CallPair {$call -> $response}';
+}
diff --git a/lib/src/invocation_matcher.dart b/lib/src/invocation_matcher.dart
index b3ff2fe..eebd297 100644
--- a/lib/src/invocation_matcher.dart
+++ b/lib/src/invocation_matcher.dart
@@ -15,6 +15,7 @@
import 'package:collection/collection.dart';
import 'package:matcher/matcher.dart';
import 'package:meta/meta.dart';
+import 'package:mockito/src/mock.dart';
/// Returns a matcher that expects an invocation that matches arguments given.
///
@@ -161,16 +162,25 @@
.equals(_invocation.namedArguments, item.namedArguments);
}
-class _MatcherEquality extends DefaultEquality /* <Matcher | E> */ {
+// Uses both DeepCollectionEquality and custom matching for invocation matchers.
+class _MatcherEquality extends DeepCollectionEquality /* <Matcher | E> */ {
const _MatcherEquality();
@override
bool equals(e1, e2) {
+ // All argument matches are wrapped in `ArgMatcher`, so we have to unwrap
+ // them into the raw `Matcher` type in order to finish our equality checks.
+ if (e1 is ArgMatcher) {
+ e1 = e1.matcher;
+ }
+ if (e2 is ArgMatcher) {
+ e2 = e2.matcher;
+ }
if (e1 is Matcher && e2 is! Matcher) {
- return e1.matches(e2, const {});
+ return e1.matches(e2, {});
}
if (e2 is Matcher && e1 is! Matcher) {
- return e2.matches(e1, const {});
+ return e2.matches(e1, {});
}
return super.equals(e1, e2);
}
diff --git a/lib/src/mock.dart b/lib/src/mock.dart
index 5af92aa..f9c4632 100644
--- a/lib/src/mock.dart
+++ b/lib/src/mock.dart
@@ -16,6 +16,8 @@
// lib/mockito.dart, which is used for Dart AOT projects such as Flutter.
import 'package:meta/meta.dart';
+import 'package:mockito/src/call_pair.dart';
+import 'package:mockito/src/invocation_matcher.dart';
import 'package:test/test.dart';
bool _whenInProgress = false;
@@ -24,16 +26,19 @@
final List<_VerifyCall> _verifyCalls = <_VerifyCall>[];
final _TimeStampProvider _timer = new _TimeStampProvider();
final List _capturedArgs = [];
-final List<_ArgMatcher> _typedArgs = <_ArgMatcher>[];
-final Map<String, _ArgMatcher> _typedNamedArgs = <String, _ArgMatcher>{};
+final List<ArgMatcher> _typedArgs = <ArgMatcher>[];
+final Map<String, ArgMatcher> _typedNamedArgs = <String, ArgMatcher>{};
// Hidden from the public API, used by spy.dart.
-void setDefaultResponse(Mock mock, CannedResponse defaultResponse()) {
+void setDefaultResponse(Mock mock, CallPair defaultResponse()) {
mock._defaultResponse = defaultResponse;
}
-throwOnMissingStub(Mock mock) {
- mock._defaultResponse = Mock._throwResponse;
+/// Opt-into [Mock] throwing [NoSuchMethodError] for unimplemented methods.
+///
+/// The default behavior when not using this is to always return `null`.
+void throwOnMissingStub(Mock mock) {
+ mock._defaultResponse = () => new CallPair.allInvocations(mock._noSuchMethod);
}
/// Extend or mixin this class to mark the implementation as a [Mock].
@@ -68,25 +73,19 @@
/// used within the context of Dart for Web (dart2js/ddc) and Dart for Mobile
/// (flutter).
class Mock {
- static CannedResponse _nullResponse() =>
- new CannedResponse(null, (_) => null);
+ static _answerNull(_) => null;
- static CannedResponse _throwResponse() => new CannedResponse(
- null,
- (Invocation inv) =>
- throw new UnimplementedError('''No stub for invocation:
- member name: ${inv.memberName}
- positional arguments (${inv.positionalArguments.length}): ${inv.positionalArguments}
- named arguments (${inv.namedArguments.length}): ${inv.namedArguments}'''));
+ static const _nullResponse = const CallPair.allInvocations(_answerNull);
- final List<RealCall> _realCalls = <RealCall>[];
- final List<CannedResponse> _responses = <CannedResponse>[];
+ final _realCalls = <RealCall>[];
+ final _responses = <CallPair>[];
+
String _givenName;
int _givenHashCode;
- _ReturnsCannedResponse _defaultResponse = _nullResponse;
+ _ReturnsCannedResponse _defaultResponse = () => _nullResponse;
- void _setExpected(CannedResponse cannedResponse) {
+ void _setExpected(CallPair cannedResponse) {
_responses.add(cannedResponse);
}
@@ -106,13 +105,16 @@
} else {
_realCalls.add(new RealCall(this, invocation));
var cannedResponse = _responses.lastWhere(
- (cr) => cr.matcher.matches(invocation),
+ (cr) => cr.call.matches(invocation, {}),
orElse: _defaultResponse);
var response = cannedResponse.response(invocation);
return response;
}
}
+ _noSuchMethod(Invocation invocation) =>
+ const Object().noSuchMethod(invocation);
+
@override
int get hashCode => _givenHashCode == null ? 0 : _givenHashCode;
@@ -125,7 +127,7 @@
String toString() => _givenName != null ? _givenName : runtimeType.toString();
}
-typedef CannedResponse _ReturnsCannedResponse();
+typedef CallPair _ReturnsCannedResponse();
// When using the typed() matcher, we transform our invocation to have knowledge
// of which arguments are wrapped with typed() and which ones are not. Otherwise
@@ -340,7 +342,7 @@
int index = 0;
for (var roleArg in roleInvocation.positionalArguments) {
var actArg = invocation.positionalArguments[index];
- if (roleArg is _ArgMatcher && roleArg._capture) {
+ if (roleArg is ArgMatcher && roleArg._capture) {
_capturedArgs.add(actArg);
}
index++;
@@ -348,8 +350,8 @@
for (var roleKey in roleInvocation.namedArguments.keys) {
var roleArg = roleInvocation.namedArguments[roleKey];
var actArg = invocation.namedArguments[roleKey];
- if (roleArg is _ArgMatcher) {
- if (roleArg is _ArgMatcher && roleArg._capture) {
+ if (roleArg is ArgMatcher) {
+ if (roleArg is ArgMatcher && roleArg._capture) {
_capturedArgs.add(actArg);
}
}
@@ -390,21 +392,14 @@
}
bool isMatchingArg(roleArg, actArg) {
- if (roleArg is _ArgMatcher) {
- return roleArg._matcher.matches(actArg, {});
+ if (roleArg is ArgMatcher) {
+ return roleArg.matcher.matches(actArg, {});
} else {
return equals(roleArg).matches(actArg, {});
}
}
}
-class CannedResponse {
- InvocationMatcher matcher;
- Answering response;
-
- CannedResponse(this.matcher, this.response);
-}
-
class _TimeStampProvider {
int _now = 0;
DateTime now() {
@@ -470,8 +465,7 @@
_WhenCall(this.mock, this.whenInvocation);
void _setExpected(Answering answer) {
- mock._setExpected(
- new CannedResponse(new InvocationMatcher(whenInvocation), answer));
+ mock._setExpected(new CallPair(isInvocation(whenInvocation), answer));
}
}
@@ -513,30 +507,33 @@
}
}
-class _ArgMatcher {
- final Matcher _matcher;
+class ArgMatcher {
+ final Matcher matcher;
final bool _capture;
- _ArgMatcher(this._matcher, this._capture);
+ ArgMatcher(this.matcher, this._capture);
+
+ @override
+ String toString() => '$ArgMatcher {$matcher: $_capture}';
}
/// An argument matcher that matches any argument passed in "this" position.
-get any => new _ArgMatcher(anything, false);
+get any => new ArgMatcher(anything, false);
/// An argument matcher that matches any argument passed in "this" position, and
/// captures the argument for later access with `captured`.
-get captureAny => new _ArgMatcher(anything, true);
+get captureAny => new ArgMatcher(anything, true);
/// An argument matcher that matches an argument that matches [matcher].
-argThat(Matcher matcher) => new _ArgMatcher(matcher, false);
+argThat(Matcher matcher) => new ArgMatcher(matcher, false);
/// An argument matcher that matches an argument that matches [matcher], and
/// captures the argument for later access with `captured`.
-captureThat(Matcher matcher) => new _ArgMatcher(matcher, true);
+captureThat(Matcher matcher) => new ArgMatcher(matcher, true);
/// A Strong-mode safe argument matcher that wraps other argument matchers.
/// See the README for a full explanation.
-/*=T*/ typed/*<T>*/(_ArgMatcher matcher, {String named}) {
+/*=T*/ typed/*<T>*/(ArgMatcher matcher, {String named}) {
if (named == null) {
_typedArgs.add(matcher);
} else {
diff --git a/lib/src/spy.dart b/lib/src/spy.dart
index 3abe37d..2f9b6c1 100644
--- a/lib/src/spy.dart
+++ b/lib/src/spy.dart
@@ -16,7 +16,9 @@
// bringing in the mirrors dependency into mockito.dart.
import 'dart:mirrors';
-import 'mock.dart' show CannedResponse, Mock, setDefaultResponse;
+import 'mock.dart' show Mock, setDefaultResponse;
+
+import 'package:mockito/src/call_pair.dart';
/// Sets the default response of [mock] to be delegated to [spyOn].
///
@@ -27,8 +29,8 @@
E spy<E>(Mock mock, E spyOn) {
var mirror = reflect(spyOn);
setDefaultResponse(
- mock,
- () => new CannedResponse(null,
- (Invocation realInvocation) => mirror.delegate(realInvocation)));
+ mock,
+ () => new CallPair.allInvocations(mirror.delegate),
+ );
return mock as E;
}
diff --git a/test/mockito_test.dart b/test/mockito_test.dart
index c69f950..49a4edc 100644
--- a/test/mockito_test.dart
+++ b/test/mockito_test.dart
@@ -622,8 +622,10 @@
test("should throw when a mock was called without a matching stub", () {
throwOnMissingStub(mock as Mock);
when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer");
- expect(() => (mock as MockedClass).methodWithoutArgs(),
- throwsUnimplementedError);
+ expect(
+ () => (mock as MockedClass).methodWithoutArgs(),
+ throwsNoSuchMethodError,
+ );
});
test("should not throw when a mock was called with a matching stub", () {