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", () {