| // Copyright 2016 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 'dart:async'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:mockito/src/call_pair.dart'; |
| import 'package:mockito/src/invocation_matcher.dart'; |
| import 'package:test_api/fake.dart'; |
| // ignore: deprecated_member_use |
| import 'package:test_api/test_api.dart'; |
| // TODO(srawlins): Remove this when we no longer need to check for an |
| // incompatiblity between test_api and test. |
| // https://github.com/dart-lang/mockito/issues/175 |
| import 'package:test_api/src/backend/invoker.dart'; |
| |
| bool _whenInProgress = false; |
| bool _untilCalledInProgress = false; |
| bool _verificationInProgress = false; |
| _WhenCall _whenCall; |
| _UntilCall _untilCall; |
| final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; |
| final _TimeStampProvider _timer = _TimeStampProvider(); |
| final List<dynamic> _capturedArgs = []; |
| final List<ArgMatcher> _storedArgs = <ArgMatcher>[]; |
| final Map<String, ArgMatcher> _storedNamedArgs = <String, ArgMatcher>{}; |
| |
| @Deprecated( |
| 'This function is not a supported function, and may be deleted as early as ' |
| 'Mockito 5.0.0') |
| void setDefaultResponse( |
| Mock mock, CallPair<dynamic> Function() defaultResponse) { |
| mock._defaultResponse = defaultResponse; |
| } |
| |
| /// 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 = |
| () => CallPair<dynamic>.allInvocations(mock._noSuchMethod); |
| } |
| |
| /// Extend or mixin this class to mark the implementation as a [Mock]. |
| /// |
| /// A mocked class implements all fields and methods with a default |
| /// implementation that does not throw a [NoSuchMethodError], and may be further |
| /// customized at runtime to define how it may behave using [when]. |
| /// |
| /// __Example use__: |
| /// |
| /// // Real class. |
| /// class Cat { |
| /// String getSound(String suffix) => 'Meow$suffix'; |
| /// } |
| /// |
| /// // Mock class. |
| /// class MockCat extends Mock implements Cat {} |
| /// |
| /// void main() { |
| /// // Create a new mocked Cat at runtime. |
| /// var cat = new MockCat(); |
| /// |
| /// // When 'getSound' is called, return 'Woof' |
| /// when(cat.getSound(any)).thenReturn('Woof'); |
| /// |
| /// // Try making a Cat sound... |
| /// print(cat.getSound('foo')); // Prints 'Woof' |
| /// } |
| /// |
| /// A class which `extends Mock` should not have any directly implemented |
| /// overridden fields or methods. These fields would not be usable as a [Mock] |
| /// with [verify] or [when]. To implement a subset of an interface manually use |
| /// [Fake] instead. |
| /// |
| /// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of |
| /// runtime reflection, and causes sub-standard code to be generated. As such, |
| /// [Mock] should strictly _not_ be used in any production code, especially if |
| /// used within the context of Dart for Web (dart2js, DDC) and Dart for Mobile |
| /// (Flutter). |
| class Mock { |
| static Null _answerNull(_) => null; |
| |
| static const _nullResponse = CallPair<Null>.allInvocations(_answerNull); |
| |
| final StreamController<Invocation> _invocationStreamController = |
| StreamController.broadcast(); |
| final _realCalls = <RealCall>[]; |
| final _responses = <CallPair<dynamic>>[]; |
| |
| String _givenName; |
| int _givenHashCode; |
| |
| _ReturnsCannedResponse _defaultResponse = () => _nullResponse; |
| |
| void _setExpected(CallPair<dynamic> cannedResponse) { |
| _responses.add(cannedResponse); |
| } |
| |
| /// Handles method stubbing, method call verification, and real method calls. |
| /// |
| /// If passed, [returnValue] will be returned during method stubbing and |
| /// method call verification. This is useful in cases where the method |
| /// invocation which led to `noSuchMethod` being called has a non-nullable |
| /// return type. |
| @override |
| @visibleForTesting |
| dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) { |
| // noSuchMethod is that 'magic' that allows us to ignore implementing fields |
| // and methods and instead define them later at compile-time per instance. |
| invocation = _useMatchedInvocationIfSet(invocation); |
| if (_whenInProgress) { |
| _whenCall = _WhenCall(this, invocation); |
| return returnValue; |
| } else if (_verificationInProgress) { |
| _verifyCalls.add(_VerifyCall(this, invocation)); |
| return returnValue; |
| } else if (_untilCalledInProgress) { |
| _untilCall = _UntilCall(this, invocation); |
| return returnValue; |
| } else { |
| _realCalls.add(RealCall(this, invocation)); |
| _invocationStreamController.add(invocation); |
| var cannedResponse = _responses.lastWhere( |
| (cr) => cr.call.matches(invocation, {}), |
| orElse: _defaultResponse); |
| var response = cannedResponse.response(invocation); |
| return response; |
| } |
| } |
| |
| dynamic _noSuchMethod(Invocation invocation) => |
| const Object().noSuchMethod(invocation); |
| |
| @override |
| int get hashCode => _givenHashCode ?? 0; |
| |
| @override |
| bool operator ==(other) => (_givenHashCode != null && other is Mock) |
| ? _givenHashCode == other._givenHashCode |
| : identical(this, other); |
| |
| @override |
| String toString() => _givenName ?? runtimeType.toString(); |
| |
| String _realCallsToString([Iterable<RealCall> realCalls]) { |
| var stringRepresentations = |
| (realCalls ?? _realCalls).map((call) => call.toString()); |
| if (stringRepresentations.any((s) => s.contains('\n'))) { |
| // As each call contains newlines, put each on its own line, for better |
| // readability. |
| return stringRepresentations.join(',\n'); |
| } else { |
| // A compact String should be perfect. |
| return stringRepresentations.join(', '); |
| } |
| } |
| |
| String _unverifiedCallsToString() => |
| _realCallsToString(_realCalls.where((call) => !call.verified)); |
| } |
| |
| typedef _ReturnsCannedResponse = CallPair<dynamic> Function(); |
| |
| // When using an [ArgMatcher], we transform our invocation to have knowledge of |
| // which arguments are wrapped, and which ones are not. Otherwise we just use |
| // the existing invocation object. |
| Invocation _useMatchedInvocationIfSet(Invocation invocation) { |
| if (_storedArgs.isNotEmpty || _storedNamedArgs.isNotEmpty) { |
| invocation = _InvocationForMatchedArguments(invocation); |
| } |
| return invocation; |
| } |
| |
| /// An Invocation implementation that takes arguments from [_storedArgs] and |
| /// [_storedNamedArgs]. |
| class _InvocationForMatchedArguments extends Invocation { |
| @override |
| final Symbol memberName; |
| @override |
| final Map<Symbol, dynamic> namedArguments; |
| @override |
| final List<dynamic> positionalArguments; |
| @override |
| final bool isGetter; |
| @override |
| final bool isMethod; |
| @override |
| final bool isSetter; |
| |
| factory _InvocationForMatchedArguments(Invocation invocation) { |
| if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { |
| throw StateError( |
| '_InvocationForMatchedArguments called when no ArgMatchers have been saved.'); |
| } |
| |
| // Handle named arguments first, so that we can provide useful errors for |
| // the various bad states. If all is well with the named arguments, then we |
| // can process the positional arguments, and resort to more general errors |
| // if the state is still bad. |
| var namedArguments = _reconstituteNamedArgs(invocation); |
| var positionalArguments = _reconstitutePositionalArgs(invocation); |
| |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| |
| return _InvocationForMatchedArguments._( |
| invocation.memberName, |
| positionalArguments, |
| namedArguments, |
| invocation.isGetter, |
| invocation.isMethod, |
| invocation.isSetter); |
| } |
| |
| // Reconstitutes the named arguments in an invocation from |
| // [_storedNamedArgs]. |
| // |
| // The `namedArguments` in [invocation] which are null should be represented |
| // by a stored value in [_storedNamedArgs]. |
| static Map<Symbol, dynamic> _reconstituteNamedArgs(Invocation invocation) { |
| final namedArguments = <Symbol, dynamic>{}; |
| final storedNamedArgSymbols = |
| _storedNamedArgs.keys.map((name) => Symbol(name)); |
| |
| // Iterate through [invocation]'s named args, validate them, and add them |
| // to the return map. |
| invocation.namedArguments.forEach((name, arg) { |
| if (arg == null) { |
| if (!storedNamedArgSymbols.contains(name)) { |
| // Either this is a parameter with default value `null`, or a `null` |
| // argument was passed, or an unnamed ArgMatcher was used. Just use |
| // `null`. |
| namedArguments[name] = null; |
| } |
| } else { |
| // Add each real named argument (not wrapped in an ArgMatcher). |
| namedArguments[name] = arg; |
| } |
| }); |
| |
| // Iterate through the stored named args, validate them, and add them to |
| // the return map. |
| _storedNamedArgs.forEach((name, arg) { |
| var nameSymbol = Symbol(name); |
| if (!invocation.namedArguments.containsKey(nameSymbol)) { |
| // Clear things out for the next call. |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| throw ArgumentError( |
| 'An ArgumentMatcher was declared as named $name, but was not ' |
| 'passed as an argument named $name.\n\n' |
| 'BAD: when(obj.fn(anyNamed: "a")))\n' |
| 'GOOD: when(obj.fn(a: anyNamed: "a")))'); |
| } |
| if (invocation.namedArguments[nameSymbol] != null) { |
| // Clear things out for the next call. |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| throw ArgumentError( |
| 'An ArgumentMatcher was declared as named $name, but a different ' |
| 'value (${invocation.namedArguments[nameSymbol]}) was passed as ' |
| '$name.\n\n' |
| 'BAD: when(obj.fn(b: anyNamed("a")))\n' |
| 'GOOD: when(obj.fn(b: anyNamed("b")))'); |
| } |
| namedArguments[nameSymbol] = arg; |
| }); |
| |
| return namedArguments; |
| } |
| |
| static List<dynamic> _reconstitutePositionalArgs(Invocation invocation) { |
| final positionalArguments = <dynamic>[]; |
| final nullPositionalArguments = |
| invocation.positionalArguments.where((arg) => arg == null); |
| if (_storedArgs.length > nullPositionalArguments.length) { |
| // More _positional_ ArgMatchers were stored than were actually passed as |
| // positional arguments. There are three ways this call could have been |
| // parsed and resolved: |
| // |
| // * an ArgMatcher was passed in [invocation] as a named argument, but |
| // without a name, and thus stored in [_storedArgs], something like |
| // `when(obj.fn(a: any))`, |
| // * an ArgMatcher was passed in an expression which was passed in |
| // [invocation], and thus stored in [_storedArgs], something like |
| // `when(obj.fn(Foo(any)))`, or |
| // * a combination of the above. |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| throw ArgumentError( |
| 'An argument matcher (like `any`) was either not used as an ' |
| 'immediate argument to ${invocation.memberName} (argument matchers ' |
| 'can only be used as an argument for the very method being stubbed ' |
| 'or verified), or was used as a named argument without the Mockito ' |
| '"named" API (Each argument matcher that is used as a named argument ' |
| 'needs to specify the name of the argument it is being used in. For ' |
| 'example: `when(obj.fn(x: anyNamed("x")))`).'); |
| } |
| var storedIndex = 0; |
| var positionalIndex = 0; |
| while (storedIndex < _storedArgs.length && |
| positionalIndex < invocation.positionalArguments.length) { |
| var arg = _storedArgs[storedIndex]; |
| if (invocation.positionalArguments[positionalIndex] == null) { |
| // Add the [ArgMatcher] given to the argument matching helper. |
| positionalArguments.add(arg); |
| storedIndex++; |
| positionalIndex++; |
| } else { |
| // An argument matching helper was not used; add the [ArgMatcher] from |
| // [invocation]. |
| positionalArguments |
| .add(invocation.positionalArguments[positionalIndex]); |
| positionalIndex++; |
| } |
| } |
| while (positionalIndex < invocation.positionalArguments.length) { |
| // Some trailing non-ArgMatcher arguments. |
| positionalArguments.add(invocation.positionalArguments[positionalIndex]); |
| positionalIndex++; |
| } |
| |
| return positionalArguments; |
| } |
| |
| _InvocationForMatchedArguments._(this.memberName, this.positionalArguments, |
| this.namedArguments, this.isGetter, this.isMethod, this.isSetter); |
| } |
| |
| @Deprecated( |
| 'This function does not provide value; hashCode and toString() can be ' |
| 'stubbed individually. This function may be deleted as early as Mockito ' |
| '5.0.0') |
| T named<T extends Mock>(T mock, {String name, int hashCode}) => mock |
| .._givenName = name |
| .._givenHashCode = hashCode; |
| |
| /// Clear stubs of, and collected interactions with [mock]. |
| void reset(var mock) { |
| mock._realCalls.clear(); |
| mock._responses.clear(); |
| } |
| |
| /// Clear the collected interactions with [mock]. |
| void clearInteractions(var mock) { |
| mock._realCalls.clear(); |
| } |
| |
| class PostExpectation<T> { |
| /// Store a canned response for this method stub. |
| /// |
| /// Note: [expected] cannot be a Future or Stream, due to Zone considerations. |
| /// To return a Future or Stream from a method stub, use [thenAnswer]. |
| void thenReturn(T expected) { |
| if (expected is Future) { |
| throw ArgumentError('`thenReturn` should not be used to return a Future. ' |
| 'Instead, use `thenAnswer((_) => future)`.'); |
| } |
| if (expected is Stream) { |
| throw ArgumentError('`thenReturn` should not be used to return a Stream. ' |
| 'Instead, use `thenAnswer((_) => stream)`.'); |
| } |
| return _completeWhen((_) => expected); |
| } |
| |
| /// Store an exception to throw when this method stub is called. |
| void thenThrow(throwable) { |
| return _completeWhen((_) { |
| throw throwable; |
| }); |
| } |
| |
| /// Store a function which is called when this method stub is called. |
| /// |
| /// The function will be called, and the return value will be returned. |
| void thenAnswer(Answering<T> answer) { |
| return _completeWhen(answer); |
| } |
| |
| void _completeWhen(Answering<T> answer) { |
| if (_whenCall == null) { |
| throw StateError( |
| 'No method stub was called from within `when()`. Was a real method ' |
| 'called, or perhaps an extension method?'); |
| } |
| _whenCall._setExpected<T>(answer); |
| _whenCall = null; |
| _whenInProgress = false; |
| } |
| } |
| |
| class InvocationMatcher { |
| final Invocation roleInvocation; |
| |
| InvocationMatcher(this.roleInvocation); |
| |
| bool matches(Invocation invocation) { |
| var isMatching = |
| _isMethodMatches(invocation) && _isArgumentsMatches(invocation); |
| if (isMatching) { |
| _captureArguments(invocation); |
| } |
| return isMatching; |
| } |
| |
| bool _isMethodMatches(Invocation invocation) { |
| if (invocation.memberName != roleInvocation.memberName) { |
| return false; |
| } |
| if ((invocation.isGetter != roleInvocation.isGetter) || |
| (invocation.isSetter != roleInvocation.isSetter) || |
| (invocation.isMethod != roleInvocation.isMethod)) { |
| return false; |
| } |
| return true; |
| } |
| |
| void _captureArguments(Invocation invocation) { |
| var index = 0; |
| for (var roleArg in roleInvocation.positionalArguments) { |
| var actArg = invocation.positionalArguments[index]; |
| if (roleArg is ArgMatcher && roleArg._capture) { |
| _capturedArgs.add(actArg); |
| } |
| index++; |
| } |
| 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) { |
| _capturedArgs.add(actArg); |
| } |
| } |
| } |
| } |
| |
| bool _isArgumentsMatches(Invocation invocation) { |
| if (invocation.positionalArguments.length != |
| roleInvocation.positionalArguments.length) { |
| return false; |
| } |
| if (invocation.namedArguments.length != |
| roleInvocation.namedArguments.length) { |
| return false; |
| } |
| var index = 0; |
| for (var roleArg in roleInvocation.positionalArguments) { |
| var actArg = invocation.positionalArguments[index]; |
| if (!isMatchingArg(roleArg, actArg)) { |
| return false; |
| } |
| index++; |
| } |
| Set roleKeys = roleInvocation.namedArguments.keys.toSet(); |
| Set actKeys = invocation.namedArguments.keys.toSet(); |
| if (roleKeys.difference(actKeys).isNotEmpty || |
| actKeys.difference(roleKeys).isNotEmpty) { |
| return false; |
| } |
| for (var roleKey in roleInvocation.namedArguments.keys) { |
| var roleArg = roleInvocation.namedArguments[roleKey]; |
| var actArg = invocation.namedArguments[roleKey]; |
| if (!isMatchingArg(roleArg, actArg)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool isMatchingArg(roleArg, actArg) { |
| if (roleArg is ArgMatcher) { |
| return roleArg.matcher.matches(actArg, {}); |
| } else { |
| return equals(roleArg).matches(actArg, {}); |
| } |
| } |
| } |
| |
| class _TimeStampProvider { |
| int _now = 0; |
| DateTime now() { |
| var candidate = DateTime.now(); |
| if (candidate.millisecondsSinceEpoch <= _now) { |
| candidate = DateTime.fromMillisecondsSinceEpoch(_now + 1); |
| } |
| _now = candidate.millisecondsSinceEpoch; |
| return candidate; |
| } |
| } |
| |
| class RealCall { |
| final Mock mock; |
| final Invocation invocation; |
| final DateTime timeStamp; |
| |
| bool verified = false; |
| |
| RealCall(this.mock, this.invocation) : timeStamp = _timer.now(); |
| |
| @override |
| String toString() { |
| var argString = ''; |
| var args = invocation.positionalArguments.map((v) => '$v'); |
| if (args.any((arg) => arg.contains('\n'))) { |
| // As one or more arg contains newlines, put each on its own line, and |
| // indent each, for better readability. |
| argString += '\n' + |
| args |
| .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')) |
| .join(',\n'); |
| } else { |
| // A compact String should be perfect. |
| argString += args.join(', '); |
| } |
| if (invocation.namedArguments.isNotEmpty) { |
| if (argString.isNotEmpty) argString += ', '; |
| var namedArgs = invocation.namedArguments.keys.map((key) => |
| '${_symbolToString(key)}: ${invocation.namedArguments[key]}'); |
| if (namedArgs.any((arg) => arg.contains('\n'))) { |
| // As one or more arg contains newlines, put each on its own line, and |
| // indent each, for better readability. |
| namedArgs = namedArgs |
| .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); |
| argString += '{\n${namedArgs.join(',\n')}}'; |
| } else { |
| // A compact String should be perfect. |
| argString += '{${namedArgs.join(', ')}}'; |
| } |
| } |
| |
| var method = _symbolToString(invocation.memberName); |
| if (invocation.isMethod) { |
| method = '$method($argString)'; |
| } else if (invocation.isGetter) { |
| method = '$method'; |
| } else if (invocation.isSetter) { |
| method = '$method=$argString'; |
| } else { |
| throw StateError('Invocation should be getter, setter or a method call.'); |
| } |
| |
| var verifiedText = verified ? '[VERIFIED] ' : ''; |
| return '$verifiedText$mock.$method'; |
| } |
| |
| // This used to use MirrorSystem, which cleans up the Symbol() wrapper. |
| // Since this toString method is just used in Mockito's own tests, it's not |
| // a big deal to massage the toString a bit. |
| // |
| // Input: Symbol("someMethodName") |
| static String _symbolToString(Symbol symbol) => |
| symbol.toString().split('"')[1]; |
| } |
| |
| class _WhenCall { |
| final Mock mock; |
| final Invocation whenInvocation; |
| _WhenCall(this.mock, this.whenInvocation); |
| |
| void _setExpected<T>(Answering<T> answer) { |
| mock._setExpected(CallPair<T>(isInvocation(whenInvocation), answer)); |
| } |
| } |
| |
| class _UntilCall { |
| final InvocationMatcher _invocationMatcher; |
| final Mock _mock; |
| |
| _UntilCall(this._mock, Invocation invocation) |
| : _invocationMatcher = InvocationMatcher(invocation); |
| |
| bool _matchesInvocation(RealCall realCall) => |
| _invocationMatcher.matches(realCall.invocation); |
| |
| List<RealCall> get _realCalls => _mock._realCalls; |
| |
| Future<Invocation> get invocationFuture { |
| if (_realCalls.any(_matchesInvocation)) { |
| return Future.value(_realCalls.firstWhere(_matchesInvocation).invocation); |
| } |
| |
| return _mock._invocationStreamController.stream |
| .firstWhere(_invocationMatcher.matches); |
| } |
| } |
| |
| class _VerifyCall { |
| final Mock mock; |
| final Invocation verifyInvocation; |
| List<RealCall> matchingInvocations; |
| |
| _VerifyCall(this.mock, this.verifyInvocation) { |
| var expectedMatcher = InvocationMatcher(verifyInvocation); |
| matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { |
| return !recordedInvocation.verified && |
| expectedMatcher.matches(recordedInvocation.invocation); |
| }).toList(); |
| } |
| |
| RealCall _findAfter(DateTime dt) { |
| return matchingInvocations |
| .firstWhere((inv) => !inv.verified && inv.timeStamp.isAfter(dt)); |
| } |
| |
| void _checkWith(bool never) { |
| if (!never && matchingInvocations.isEmpty) { |
| var message; |
| if (mock._realCalls.isEmpty) { |
| message = 'No matching calls (actually, no calls at all).'; |
| } else { |
| var otherCalls = mock._realCallsToString(); |
| message = 'No matching calls. All calls: $otherCalls'; |
| } |
| fail('$message\n' |
| '(If you called `verify(...).called(0);`, please instead use ' |
| '`verifyNever(...);`.)'); |
| } |
| if (never && matchingInvocations.isNotEmpty) { |
| var calls = mock._unverifiedCallsToString(); |
| fail('Unexpected calls: $calls'); |
| } |
| matchingInvocations.forEach((inv) { |
| inv.verified = true; |
| }); |
| } |
| |
| @override |
| String toString() => |
| 'VerifyCall<mock: $mock, memberName: ${verifyInvocation.memberName}>'; |
| } |
| |
| // An argument matcher that acts like an argument during stubbing or |
| // verification, and stores "matching" information. |
| // |
| /// Users do not need to construct this manually; users can instead use the |
| /// built-in values, [any], [anyNamed], [captureAny], [captureAnyNamed], or the |
| /// functions [argThat] and [captureThat]. |
| class ArgMatcher { |
| final Matcher matcher; |
| final bool _capture; |
| |
| ArgMatcher(this.matcher, this._capture); |
| |
| @override |
| String toString() => '$ArgMatcher {$matcher: $_capture}'; |
| } |
| |
| /// An argument matcher that matches any argument passed in "this" position. |
| Null get any => _registerMatcher(anything, false, argumentMatcher: 'any'); |
| |
| /// An argument matcher that matches any named argument passed in for the |
| /// parameter named [named]. |
| Null anyNamed(String named) => _registerMatcher(anything, false, |
| named: named, argumentMatcher: 'anyNamed'); |
| |
| /// An argument matcher that matches any argument passed in "this" position, and |
| /// captures the argument for later access with `captured`. |
| Null get captureAny => |
| _registerMatcher(anything, true, argumentMatcher: 'captureAny'); |
| |
| /// An argument matcher that matches any named argument passed in for the |
| /// parameter named [named], and captures the argument for later access with |
| /// `captured`. |
| Null captureAnyNamed(String named) => _registerMatcher(anything, true, |
| named: named, argumentMatcher: 'captureAnyNamed'); |
| |
| /// An argument matcher that matches an argument that matches [matcher]. |
| Null argThat(Matcher matcher, {String named}) => |
| _registerMatcher(matcher, false, named: named, argumentMatcher: 'argThat'); |
| |
| /// An argument matcher that matches an argument that matches [matcher], and |
| /// captures the argument for later access with `captured`. |
| Null captureThat(Matcher matcher, {String named}) => |
| _registerMatcher(matcher, true, |
| named: named, argumentMatcher: 'captureThat'); |
| |
| @Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') |
| Null typed<T>(ArgMatcher matcher, {String named}) => null; |
| |
| @Deprecated('Replace with `argThat`') |
| Null typedArgThat(Matcher matcher, {String named}) => |
| argThat(matcher, named: named); |
| |
| @Deprecated('Replace with `captureThat`') |
| Null typedCaptureThat(Matcher matcher, {String named}) => |
| captureThat(matcher, named: named); |
| |
| /// Registers [matcher] into the stored arguments collections. |
| /// |
| /// Creates an [ArgMatcher] with [matcher] and [capture], then if [named] is |
| /// non-null, stores that into the positional stored arguments list; otherwise |
| /// stores it into the named stored arguments map, keyed on [named]. |
| /// [argumentMatcher] is the name of the public API used to register [matcher], |
| /// for error messages. |
| Null _registerMatcher(Matcher matcher, bool capture, |
| {String named, String argumentMatcher}) { |
| if (!_whenInProgress && !_untilCalledInProgress && !_verificationInProgress) { |
| // It is not meaningful to store argument matchers outside of stubbing |
| // (`when`), or verification (`verify` and `untilCalled`). Such argument |
| // matchers will be processed later erroneously. |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| throw ArgumentError( |
| 'The "$argumentMatcher" argument matcher is used outside of method ' |
| 'stubbing (via `when`) or verification (via `verify` or `untilCalled`). ' |
| 'This is invalid, and results in bad behavior during the next stubbing ' |
| 'or verification.'); |
| } |
| var argMatcher = ArgMatcher(matcher, capture); |
| if (named == null) { |
| _storedArgs.add(argMatcher); |
| } else { |
| _storedNamedArgs[named] = argMatcher; |
| } |
| return null; |
| } |
| |
| /// Information about a stub call verification. |
| /// |
| /// This class is most useful to users in two ways: |
| /// |
| /// * verifying call count, via [called], |
| /// * collecting captured arguments, via [captured]. |
| class VerificationResult { |
| List<dynamic> _captured; |
| |
| /// List of all arguments captured in real calls. |
| /// |
| /// This list will include any captured default arguments and has no |
| /// structure differentiating the arguments of one call from another. Given |
| /// the following class: |
| /// |
| /// ```dart |
| /// class C { |
| /// String methodWithPositionalArgs(int x, [int y]) => ''; |
| /// String methodWithTwoNamedArgs(int x, {int y, int z}) => ''; |
| /// } |
| /// ``` |
| /// |
| /// the following stub calls will result in the following captured arguments: |
| /// |
| /// ```dart |
| /// mock.methodWithPositionalArgs(1); |
| /// mock.methodWithPositionalArgs(2, 3); |
| /// var captured = verify( |
| /// mock.methodWithPositionalArgs(captureAny, captureAny)).captured; |
| /// print(captured); // Prints "[1, null, 2, 3]" |
| /// |
| /// mock.methodWithTwoNamedArgs(1, y: 42, z: 43); |
| /// mock.methodWithTwoNamedArgs(1, y: 44, z: 45); |
| /// var captured = verify( |
| /// mock.methodWithTwoNamedArgs(any, |
| /// y: captureAnyNamed('y'), z: captureAnyNamed('z'))).captured; |
| /// print(captured); // Prints "[42, 43, 44, 45]" |
| /// ``` |
| /// |
| /// Named arguments are listed in the order they are captured in, not the |
| /// order in which they were passed. |
| // TODO(https://github.com/dart-lang/linter/issues/1992): Remove ignore |
| // comments below when google3 has linter with this bug fixed. |
| // ignore: unnecessary_getters_setters |
| List<dynamic> get captured => _captured; |
| |
| @Deprecated( |
| 'captured should be considered final - assigning this field may be ' |
| 'removed as early as Mockito 5.0.0') |
| // ignore: unnecessary_getters_setters |
| set captured(List<dynamic> captured) => _captured = captured; |
| |
| /// The number of calls matched in this verification. |
| int callCount; |
| |
| bool _testApiMismatchHasBeenChecked = false; |
| |
| @Deprecated( |
| 'User-constructed VerificationResult is deprecated; this constructor may ' |
| 'be deleted as early as Mockito 5.0.0') |
| VerificationResult(int callCount) : this._(callCount); |
| |
| VerificationResult._(this.callCount) |
| : _captured = List<dynamic>.from(_capturedArgs, growable: false) { |
| _capturedArgs.clear(); |
| } |
| |
| /// Check for a version incompatibility between mockito, test, and test_api. |
| /// |
| /// This incompatibility results in an inscrutible error for users. Catching |
| /// it here allows us to give some steps to fix. |
| // TODO(srawlins): Remove this when we don't need to check for an |
| // incompatiblity between test_api and test any more. |
| // https://github.com/dart-lang/mockito/issues/175 |
| void _checkTestApiMismatch() { |
| try { |
| Invoker.current; |
| } on CastError catch (e) { |
| if (!e |
| .toString() |
| .contains("type 'Invoker' is not a subtype of type 'Invoker'")) { |
| // Hmm. This is a different CastError from the one we're trying to |
| // protect against. Let it go. |
| return; |
| } |
| print('Error: Package incompatibility between mockito, test, and ' |
| 'test_api packages:'); |
| print(''); |
| print('* mockito ^4.0.0 is incompatible with test <1.4.0'); |
| print('* mockito <4.0.0 is incompatible with test ^1.4.0'); |
| print(''); |
| print('As mockito does not have a dependency on the test package, ' |
| 'nothing stopped you from landing in this situation. :( ' |
| 'Apologies.'); |
| print(''); |
| print('To fix: bump your dependency on the test package to something ' |
| 'like: ^1.4.0, or downgrade your dependency on mockito to something ' |
| 'like: ^3.0.0'); |
| rethrow; |
| } |
| } |
| |
| /// Assert that the number of calls matches [matcher]. |
| /// |
| /// Examples: |
| /// |
| /// * `verify(mock.m()).called(1)` asserts that `m()` is called exactly once. |
| /// * `verify(mock.m()).called(greaterThan(2))` asserts that `m()` is called |
| /// more than two times. |
| /// |
| /// To assert that a method was called zero times, use [verifyNever]. |
| void called(dynamic matcher) { |
| if (!_testApiMismatchHasBeenChecked) { |
| // Only execute the check below once. `Invoker.current` may look like a |
| // cheap getter, but it involves Zones and casting. |
| _testApiMismatchHasBeenChecked = true; |
| _checkTestApiMismatch(); |
| } |
| expect(callCount, wrapMatcher(matcher), |
| reason: 'Unexpected number of calls'); |
| } |
| } |
| |
| typedef Answering<T> = T Function(Invocation realInvocation); |
| |
| typedef Verification = VerificationResult Function<T>(T matchingInvocations); |
| |
| typedef _InOrderVerification = void Function<T>(List<T> recordedInvocations); |
| |
| /// Verify that a method on a mock object was never called with the given |
| /// arguments. |
| /// |
| /// Call a method on a mock object within a `verifyNever` call. For example: |
| /// |
| /// ```dart |
| /// cat.eatFood("chicken"); |
| /// verifyNever(cat.eatFood("fish")); |
| /// ``` |
| /// |
| /// Mockito will pass the current test case, as `cat.eatFood` has not been |
| /// called with `"chicken"`. |
| Verification get verifyNever => _makeVerify(true); |
| |
| /// Verify that a method on a mock object was called with the given arguments. |
| /// |
| /// Call a method on a mock object within the call to `verify`. For example: |
| /// |
| /// ```dart |
| /// cat.eatFood("chicken"); |
| /// verify(cat.eatFood("fish")); |
| /// ``` |
| /// |
| /// Mockito will fail the current test case if `cat.eatFood` has not been called |
| /// with `"fish"`. Optionally, call `called` on the result, to verify that the |
| /// method was called a certain number of times. For example: |
| /// |
| /// ```dart |
| /// verify(cat.eatFood("fish")).called(2); |
| /// verify(cat.eatFood("fish")).called(greaterThan(3)); |
| /// ``` |
| /// |
| /// Note: When mockito verifies a method call, said call is then excluded from |
| /// further verifications. A single method call cannot be verified from multiple |
| /// calls to `verify`, or `verifyInOrder`. See more details in the FAQ. |
| /// |
| /// Note: because of an unintended limitation, `verify(...).called(0);` will |
| /// not work as expected. Please use `verifyNever(...);` instead. |
| /// |
| /// See also: [verifyNever], [verifyInOrder], [verifyZeroInteractions], and |
| /// [verifyNoMoreInteractions]. |
| Verification get verify => _makeVerify(false); |
| |
| Verification _makeVerify(bool never) { |
| if (_verifyCalls.isNotEmpty) { |
| var message = 'Verification appears to be in progress.'; |
| if (_verifyCalls.length == 1) { |
| message = |
| '$message One verify call has been stored: ${_verifyCalls.single}'; |
| } else { |
| message = |
| '$message ${_verifyCalls.length} verify calls have been stored. ' |
| '[${_verifyCalls.first}, ..., ${_verifyCalls.last}]'; |
| } |
| throw StateError(message); |
| } |
| _verificationInProgress = true; |
| return <T>(T mock) { |
| _verificationInProgress = false; |
| if (_verifyCalls.length == 1) { |
| var verifyCall = _verifyCalls.removeLast(); |
| var result = VerificationResult._(verifyCall.matchingInvocations.length); |
| verifyCall._checkWith(never); |
| return result; |
| } else { |
| fail('Used on a non-mockito object'); |
| } |
| }; |
| } |
| |
| /// Verify that a list of methods on a mock object have been called with the |
| /// given arguments. For example: |
| /// |
| /// ```dart |
| /// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); |
| /// ``` |
| /// |
| /// This verifies that `eatFood` was called with `"Milk"`, sound` was called |
| /// with no arguments, and `eatFood` was then called with some argument. |
| /// |
| /// Note: [verifyInOrder] only verifies that each call was made in the order |
| /// given, but not that those were the only calls. In the example above, if |
| /// other calls were made to `eatFood` or `sound` between the three given |
| /// calls, or before or after them, the verification will still succeed. |
| _InOrderVerification get verifyInOrder { |
| if (_verifyCalls.isNotEmpty) { |
| throw StateError(_verifyCalls.join()); |
| } |
| _verificationInProgress = true; |
| return <T>(List<T> _) { |
| _verificationInProgress = false; |
| var dt = DateTime.fromMillisecondsSinceEpoch(0); |
| var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); |
| _verifyCalls.clear(); |
| var matchedCalls = <RealCall>[]; |
| for (var verifyCall in tmpVerifyCalls) { |
| try { |
| var matched = verifyCall._findAfter(dt); |
| matchedCalls.add(matched); |
| dt = matched.timeStamp; |
| } on StateError { |
| var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); |
| var allInvocations = |
| mocks.expand((m) => m._realCalls).toList(growable: false); |
| allInvocations |
| .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); |
| var otherCalls = ''; |
| if (allInvocations.isNotEmpty) { |
| otherCalls = " All calls: ${allInvocations.join(", ")}"; |
| } |
| fail('Matching call #${tmpVerifyCalls.indexOf(verifyCall)} ' |
| 'not found.$otherCalls'); |
| } |
| } |
| for (var call in matchedCalls) { |
| call.verified = true; |
| } |
| }; |
| } |
| |
| void _throwMockArgumentError(String method, var nonMockInstance) { |
| if (nonMockInstance == null) { |
| throw ArgumentError('$method was called with a null argument'); |
| } |
| throw ArgumentError('$method must only be given a Mock object'); |
| } |
| |
| void verifyNoMoreInteractions(var mock) { |
| if (mock is Mock) { |
| var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); |
| if (unverified.isNotEmpty) { |
| fail('No more calls expected, but following found: ' + unverified.join()); |
| } |
| } else { |
| _throwMockArgumentError('verifyNoMoreInteractions', mock); |
| } |
| } |
| |
| void verifyZeroInteractions(var mock) { |
| if (mock is Mock) { |
| if (mock._realCalls.isNotEmpty) { |
| fail('No interaction expected, but following found: ' + |
| mock._realCalls.join()); |
| } |
| } else { |
| _throwMockArgumentError('verifyZeroInteractions', mock); |
| } |
| } |
| |
| typedef Expectation = PostExpectation<T> Function<T>(T x); |
| |
| /// Create a stub method response. |
| /// |
| /// Call a method on a mock object within the call to `when`, and call a |
| /// canned response method on the result. For example: |
| /// |
| /// ```dart |
| /// when(cat.eatFood("fish")).thenReturn(true); |
| /// ``` |
| /// |
| /// Mockito will store the fake call to `cat.eatFood`, and pair the exact |
| /// arguments given with the response. When `cat.eatFood` is called outside a |
| /// `when` or `verify` context (a call "for real"), Mockito will respond with |
| /// the stored canned response, if it can match the mock method parameters. |
| /// |
| /// The response generators include `thenReturn`, `thenAnswer`, and `thenThrow`. |
| /// |
| /// See the README for more information. |
| Expectation get when { |
| if (_whenCall != null) { |
| throw StateError('Cannot call `when` within a stub response'); |
| } |
| _whenInProgress = true; |
| return <T>(T _) { |
| _whenInProgress = false; |
| return PostExpectation<T>(); |
| }; |
| } |
| |
| typedef InvocationLoader = Future<Invocation> Function<T>(T _); |
| |
| /// Returns a future [Invocation] that will complete upon the first occurrence |
| /// of the given invocation. |
| /// |
| /// Usage of this is as follows: |
| /// |
| /// ```dart |
| /// cat.eatFood("fish"); |
| /// await untilCalled(cat.chew()); |
| /// ``` |
| /// |
| /// In the above example, the untilCalled(cat.chew()) will complete only when |
| /// that method is called. If the given invocation has already been called, the |
| /// future will return immediately. |
| InvocationLoader get untilCalled { |
| _untilCalledInProgress = true; |
| return <T>(T _) { |
| _untilCalledInProgress = false; |
| return _untilCall.invocationFuture; |
| }; |
| } |
| |
| /// Print all collected invocations of any mock methods of [mocks]. |
| void logInvocations(List<Mock> mocks) { |
| var allInvocations = |
| mocks.expand((m) => m._realCalls).toList(growable: false); |
| allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); |
| allInvocations.forEach((inv) { |
| print(inv.toString()); |
| }); |
| } |
| |
| /// Reset the state of Mockito, typically for use between tests. |
| /// |
| /// For example, when using the test package, mock methods may accumulate calls |
| /// in a `setUp` method, making it hard to verify method calls that were made |
| /// _during_ an individual test. Or, there may be unverified calls from previous |
| /// test cases that should not affect later test cases. |
| /// |
| /// In these cases, [resetMockitoState] might be called at the end of `setUp`, |
| /// or in `tearDown`. |
| void resetMockitoState() { |
| _whenInProgress = false; |
| _untilCalledInProgress = false; |
| _verificationInProgress = false; |
| _whenCall = null; |
| _untilCall = null; |
| _verifyCalls.clear(); |
| _capturedArgs.clear(); |
| _storedArgs.clear(); |
| _storedNamedArgs.clear(); |
| } |