blob: 6358ebaef53e3c723072fe6aeb5e37937a7dc0fe [file] [log] [blame]
// Copyright 2018 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:mockito/mockito.dart';
import 'package:test/test.dart';
/// Every method on this class has a nullable return type and nullable parameter
/// types. This allows each method to be verified without manual overriding or
/// code-generation.
class _RealClass {
String? methodWithoutArgs() => 'Real';
String? methodWithNormalArgs(int? x) => 'Real';
String? methodWithListArgs(List<int>? x) => 'Real';
String? methodWithOptionalArg([int? x]) => 'Real';
String? methodWithPositionalArgs(int? x, [int? y]) => 'Real';
String? methodWithNamedArgs(int x, {int? y}) => 'Real';
String? methodWithOnlyNamedArgs({int? y = 0, int? z}) => 'Real';
String? get getter => 'Real';
set setter(String arg) {
throw StateError('I must be mocked');
}
String? methodWithLongArgs(LongToString? a, LongToString? b,
{LongToString? c, LongToString? d}) =>
'Real';
}
class LongToString {
final List aList;
final Map aMap;
final String aString;
LongToString(this.aList, this.aMap, this.aString);
@override
String toString() => 'LongToString<\n'
' aList: $aList\n'
' aMap: $aMap\n'
' aString: $aString\n'
'>';
}
class _MockedClass extends Mock implements _RealClass {}
void expectFail(Pattern expectedMessage, void Function() expectedToFail) {
try {
expectedToFail();
fail('It was expected to fail!');
} on TestFailure catch (e) {
expect(e.message,
expectedMessage is String ? expectedMessage : contains(expectedMessage),
reason: 'Failed but with unexpected message');
}
}
const noMatchingCallsFooter = '(If you called `verify(...).called(0);`, '
'please instead use `verifyNever(...);`.)';
void main() {
late _MockedClass mock;
setUp(() {
mock = _MockedClass();
});
tearDown(() {
// In some of the tests that expect an Error to be thrown, Mockito's
// global state can become invalid. Reset it.
resetMockitoState();
});
group('verify', () {
test('should verify method without args', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
});
test('should verify method with normal args', () {
mock.methodWithNormalArgs(42);
verify(mock.methodWithNormalArgs(42));
});
test('should mock method with positional args', () {
mock.methodWithPositionalArgs(42, 17);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithPositionalArgs(42, 17)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithPositionalArgs(42));
});
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithPositionalArgs(42, 17)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithPositionalArgs(42, 18));
});
verify(mock.methodWithPositionalArgs(42, 17));
});
test('should mock method with named args', () {
mock.methodWithNamedArgs(42, y: 17);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithNamedArgs(42, {y: 17})\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNamedArgs(42));
});
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithNamedArgs(42, {y: 17})\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNamedArgs(42, y: 18));
});
verify(mock.methodWithNamedArgs(42, y: 17));
});
test('should mock method with list args', () {
mock.methodWithListArgs([42]);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithListArgs([42])\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithListArgs([43]));
});
verify(mock.methodWithListArgs([42]));
});
test('should mock method with argument matcher', () {
mock.methodWithNormalArgs(100);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithNormalArgs(100)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNormalArgs(argThat(greaterThan(100))));
});
verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100))));
});
test('should mock method with mix of argument matchers and real things',
() {
mock.methodWithPositionalArgs(100, 17);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithPositionalArgs(100, 17)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithPositionalArgs(
argThat(greaterThanOrEqualTo(100)), 18));
});
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithPositionalArgs(100, 17)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17));
});
verify(mock.methodWithPositionalArgs(
argThat(greaterThanOrEqualTo(100)), 17));
});
test('should mock getter', () {
mock.getter;
verify(mock.getter);
});
test('should mock setter', () {
mock.setter = 'A';
final expectedMessage = RegExp.escape('No matching calls. '
'All calls: _MockedClass.setter==\'A\'\n$noMatchingCallsFooter');
// RegExp needed because of https://github.com/dart-lang/sdk/issues/33565
var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?='));
expectFail(expectedPattern, () => verify(mock.setter = 'B'));
verify(mock.setter = 'A');
});
test('should throw meaningful errors when verification is interrupted', () {
var badHelper = () => throw 'boo';
try {
verify(mock.methodWithNamedArgs(42, y: badHelper()));
} catch (_) {}
// At this point, verification was interrupted, so
// `_verificationInProgress` is still `true`. Calling mock methods below
// adds items to `_verifyCalls`.
mock.methodWithNamedArgs(42, y: 17);
mock.methodWithNamedArgs(42, y: 17);
try {
verify(mock.methodWithNamedArgs(42, y: 17));
fail('verify call was expected to throw!');
} catch (exception) {
expect(
exception,
isA<StateError>().having(
(exception) => exception.message,
'message',
contains('Verification appears to be in progress. '
'2 verify calls have been stored.')));
}
});
test('should fail when called with non-mock-call parameter', () {
expectFail('Used on a non-mockito object', () {
verify(['a string is not a mock call']);
});
});
});
group('verify should fail when no matching call is found', () {
test('and there are no unmatched calls', () {
expectFail(
'No matching calls (actually, no calls at all).\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNormalArgs(43));
});
});
test('and there is one unmatched call', () {
mock.methodWithNormalArgs(42);
expectFail(
'No matching calls. All calls: _MockedClass.methodWithNormalArgs(42)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNormalArgs(43));
});
});
test('and there is one unmatched call without args', () {
mock.methodWithOptionalArg();
expectFail(
'No matching calls. All calls: _MockedClass.methodWithOptionalArg(null)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithOptionalArg(43));
});
});
test('and there are multiple unmatched calls', () {
mock.methodWithNormalArgs(41);
mock.methodWithNormalArgs(42);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithNormalArgs(41), '
'_MockedClass.methodWithNormalArgs(42)\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNormalArgs(43));
});
});
test('and unmatched calls have only named args', () {
mock.methodWithOnlyNamedArgs(y: 1);
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithOnlyNamedArgs({y: 1, z: null})\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithOnlyNamedArgs());
});
});
});
group('verify qualifies', () {
group('unqualified as at least one', () {
test('zero fails', () {
expectFail(
'No matching calls (actually, no calls at all).\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithoutArgs());
});
});
test('one passes', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
});
test('more than one passes', () {
mock.methodWithoutArgs();
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
});
});
group('expecting one call', () {
test('zero actual calls fails', () {
expectFail(
'No matching calls (actually, no calls at all).\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithoutArgs()).called(1);
});
});
test('one actual call passes', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs()).called(1);
});
test('more than one actual call fails', () {
mock.methodWithoutArgs();
mock.methodWithoutArgs();
expectFail('Expected: <1>\n Actual: <2>\nUnexpected number of calls\n',
() {
verify(mock.methodWithoutArgs()).called(1);
});
});
});
group('expecting more than two calls', () {
test('zero actual calls fails', () {
expectFail(
'No matching calls (actually, no calls at all).\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithoutArgs()).called(greaterThan(2));
});
});
test('one actual call fails', () {
mock.methodWithoutArgs();
expectFail(
'Expected: a value greater than <2>\n'
' Actual: <1>\n'
' Which: is not a value greater than <2>\n'
'Unexpected number of calls\n', () {
verify(mock.methodWithoutArgs()).called(greaterThan(2));
});
});
test('three actual calls passes', () {
mock.methodWithoutArgs();
mock.methodWithoutArgs();
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs()).called(greaterThan(2));
});
});
group('expecting zero calls', () {
test('zero actual calls passes', () {
expectFail(
'No matching calls (actually, no calls at all).\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithoutArgs()).called(0);
});
});
test('one actual call fails', () {
mock.methodWithoutArgs();
expectFail(
'Expected: <0>\n'
' Actual: <1>\n'
'Unexpected number of calls\n', () {
verify(mock.methodWithoutArgs()).called(0);
});
});
});
group('verifyNever', () {
test('zero passes', () {
verifyNever(mock.methodWithoutArgs());
});
test('one fails', () {
// Add one verified method that should not appear in message.
mock.methodWithNormalArgs(1);
verify(mock.methodWithNormalArgs(1)).called(1);
mock.methodWithoutArgs();
expectFail('Unexpected calls: _MockedClass.methodWithoutArgs()', () {
verifyNever(mock.methodWithoutArgs());
});
});
});
group('does not count already verified again', () {
test('fail case', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
expectFail(
'No matching calls. '
'All calls: [VERIFIED] _MockedClass.methodWithoutArgs()\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithoutArgs());
});
});
test('pass case', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
});
});
});
group('verifyZeroInteractions', () {
test('never touched pass', () {
verifyZeroInteractions(mock);
});
test('any touch fails', () {
mock.methodWithoutArgs();
expectFail(
'No interaction expected, but following found: '
'_MockedClass.methodWithoutArgs()', () {
verifyZeroInteractions(mock);
});
});
test('verified call fails', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
expectFail(
'No interaction expected, but following found: '
'[VERIFIED] _MockedClass.methodWithoutArgs()', () {
verifyZeroInteractions(mock);
});
});
});
group('verifyNoMoreInteractions', () {
test('never touched pass', () {
verifyNoMoreInteractions(mock);
});
test('any unverified touch fails', () {
mock.methodWithoutArgs();
expectFail(
'No more calls expected, but following found: '
'_MockedClass.methodWithoutArgs()', () {
verifyNoMoreInteractions(mock);
});
});
test('verified touch passes', () {
mock.methodWithoutArgs();
verify(mock.methodWithoutArgs());
verifyNoMoreInteractions(mock);
});
test('throws if given a real object', () {
expect(() => verifyNoMoreInteractions(_RealClass()), throwsArgumentError);
});
});
group('verifyInOrder', () {
test('right order passes', () {
mock.methodWithoutArgs();
mock.getter;
verifyInOrder([mock.methodWithoutArgs(), mock.getter]);
});
test('wrong order fails', () {
mock.methodWithoutArgs();
mock.getter;
expectFail(
'Matching call #1 not found. All calls: '
'_MockedClass.methodWithoutArgs(), _MockedClass.getter', () {
verifyInOrder([mock.getter, mock.methodWithoutArgs()]);
});
});
test('should fail when given non-mock-call parameters', () {
expectFail(
RegExp("'verifyInOrder' called with non-mockito stub calls; List "
'contains 3 elements, but 2 stub calls were stored'), () {
verifyInOrder([mock.getter, mock.methodWithoutArgs, mock.getter]);
});
});
test('should fail when parameters do not map to List elements', () {
expectFail(
RegExp("'verifyInOrder' called with non-mockito stub calls; List "
'contains 1 elements, but 2 stub calls were stored'), () {
verifyInOrder([
[mock.getter, mock.getter]
]);
});
});
test('verify been used as an argument fails', () {
mock.methodWithoutArgs();
mock.getter;
expectFail(
'There is already a verification in progress, '
'check if it was not called with a verify argument(s)', () {
verifyInOrder([
verify(mock.getter),
verify(mock.methodWithoutArgs()),
]);
});
});
test('incomplete fails', () {
mock.methodWithoutArgs();
expectFail(
'Matching call #1 not found. All calls: '
'_MockedClass.methodWithoutArgs()', () {
verifyInOrder([mock.methodWithoutArgs(), mock.getter]);
});
});
test('methods can be called again and again', () {
mock.methodWithoutArgs();
mock.getter;
mock.methodWithoutArgs();
verifyInOrder(
[mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]);
});
test('can return captures from capturing argument matchers', () {
mock.methodWithNormalArgs(1);
mock.methodWithoutArgs();
mock.methodWithNormalArgs(2);
var captured = verifyInOrder([
mock.methodWithNormalArgs(captureAny),
mock.methodWithoutArgs(),
mock.methodWithNormalArgs(captureAny)
]).captured;
expect(captured, hasLength(3));
expect(captured[0], equals([1]));
expect(captured[1], equals([]));
expect(captured[2], equals([2]));
});
test('methods can be called again and again - fail case', () {
mock.methodWithoutArgs();
mock.methodWithoutArgs();
mock.getter;
expectFail(
'Matching call #2 not found. All calls: '
'_MockedClass.methodWithoutArgs(), _MockedClass.methodWithoutArgs(), '
'_MockedClass.getter', () {
verifyInOrder(
[mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]);
});
});
});
group('multiline toStrings on objects', () {
test(
'"No matching calls" message visibly separates unmatched calls, '
'if an arg\'s string representations is multiline', () {
mock.methodWithLongArgs(LongToString([1, 2], {1: 'a', 2: 'b'}, 'c'),
LongToString([4, 5], {3: 'd', 4: 'e'}, 'f'));
mock.methodWithLongArgs(null, null,
c: LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'),
d: LongToString([7, 8], {7: 'j', 8: 'k'}, 'l'));
expectFail(
'No matching calls. All calls: '
'_MockedClass.methodWithLongArgs(\n'
' LongToString<\n'
' aList: [1, 2]\n'
' aMap: {1: a, 2: b}\n'
' aString: c\n'
' >,\n'
' LongToString<\n'
' aList: [4, 5]\n'
' aMap: {3: d, 4: e}\n'
' aString: f\n'
' >, {c: null, d: null}),\n'
'_MockedClass.methodWithLongArgs(null, null, {\n'
' c: LongToString<\n'
' aList: [5, 6]\n'
' aMap: {5: g, 6: h}\n'
' aString: i\n'
' >,\n'
' d: LongToString<\n'
' aList: [7, 8]\n'
' aMap: {7: j, 8: k}\n'
' aString: l\n'
' >})\n'
'$noMatchingCallsFooter', () {
verify(mock.methodWithNormalArgs(43));
});
});
});
}