From 7e372d9fea91221355b6d27d143e234dfed162ef Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 31 Jul 2017 14:58:13 -0700 Subject: [PATCH] Address the verify(...).called(0) issue. Also: (#69) * Document verifyNever * Improve the error message of verifications * Add some verification tests --- lib/src/mock.dart | 34 ++++++-- test/mockito_test.dart | 187 ++++++++++++++++++++++++++++++++++------- 2 files changed, 185 insertions(+), 36 deletions(-) diff --git a/lib/src/mock.dart b/lib/src/mock.dart index 487a7155..82d83835 100644 --- a/lib/src/mock.dart +++ b/lib/src/mock.dart @@ -491,12 +491,16 @@ class _VerifyCall { void _checkWith(bool never) { if (!never && matchingInvocations.isEmpty) { - var otherCallsText = ""; - if (mock._realCalls.isNotEmpty) { - otherCallsText = " All calls: "; + var message; + if (mock._realCalls.isEmpty) { + message = "No matching calls (actually, no calls at all)."; + } else { + var otherCalls = mock._realCalls.join(", "); + message = "No matching calls. All calls: $otherCalls"; } - var calls = mock._realCalls.join(", "); - fail("No matching calls.$otherCallsText$calls"); + fail("$message\n" + "(If you called `verify(...).called(0);`, please instead use " + "`verifyNever(...);`.)"); } if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCalls.join(", "); @@ -564,13 +568,26 @@ typedef VerificationResult Verification(matchingInvocations); typedef void _InOrderVerification(List 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 given arguments. +/// 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")); /// ``` /// @@ -583,6 +600,9 @@ Verification get verifyNever => _makeVerify(true); /// verify(cat.eatFood("fish")).called(greaterThan(3)); /// ``` /// +/// 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); @@ -601,7 +621,7 @@ Verification _makeVerify(bool never) { verifyCall._checkWith(never); return result; } else { - fail("Used on non-mockito"); + fail("Used on a non-mockito object"); } }; } diff --git a/test/mockito_test.dart b/test/mockito_test.dart index 427816ca..074632a0 100644 --- a/test/mockito_test.dart +++ b/test/mockito_test.dart @@ -72,6 +72,9 @@ expectFail(String expectedMessage, expectedToFail()) { } } +String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " + "please instead use `verifyNever(...);`.)"; + void main() { RealClass mock; @@ -98,50 +101,59 @@ void main() { when(mock.methodWithoutArgs()).thenReturn("A"); expect(mock.methodWithoutArgs(), equals("A")); }); + test("should mock method with normal args", () { when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect(mock.methodWithNormalArgs(43), isNull); expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); }); + test("should mock method with mock args", () { var m1 = new MockedClass(); when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); expect(mock.methodWithObjArgs(new MockedClass()), isNull); expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); }); + test("should mock method with positional args", () { when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); }); + test("should mock method with named args", () { when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); expect(mock.methodWithNamedArgs(42), isNull); expect(mock.methodWithNamedArgs(42, y: 18), isNull); expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); }); + test("should mock method with List args", () { when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); expect(mock.methodWithListArgs([43]), isNull); expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); }); + test("should mock method with argument matcher", () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with any argument matcher", () { when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with any list argument matcher", () { when(mock.methodWithListArgs(any)).thenReturn("A lot!"); expect(mock.methodWithListArgs([42]), equals("A lot!")); expect(mock.methodWithListArgs([43]), equals("A lot!")); }); + test("should mock method with multiple named args and matchers", () { when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); @@ -153,6 +165,7 @@ void main() { .thenReturn("x y z"); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); + test("should mock method with mix of argument matchers and real things", () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) @@ -161,64 +174,78 @@ void main() { expect(mock.methodWithPositionalArgs(101, 18), isNull); expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); }); + test("should mock getter", () { when(mock.getter).thenReturn("A"); expect(mock.getter, equals("A")); }); + test("should mock hashCode", () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); + test("should have hashCode when it is not mocked", () { expect(mock.hashCode, isNotNull); }); + // test("should n't mock toString", (){ // when(mock.toString()).thenReturn("meow"); // expect(mock.toString(), equals("meow")); // }); + test("should have default toString when it is not mocked", () { expect(mock.toString(), equals("MockedClass")); }); + test("should have toString as name when it is not mocked", () { named(mock, name: "Cat"); expect(mock.toString(), equals("Cat")); }); + test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(new MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); + test("should use identical equality between it is not mocked", () { var anotherMock = new MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); + //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); expect(mock.methodWithNormalArgs(43), equals("43")); expect(mock.methodWithNormalArgs(42), equals("42")); }); + test("should return mock to make simple oneline mocks", () { RealClass mockWithSetup = when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); + test("should use latest matching when definition", () { when(mock.methodWithoutArgs()).thenReturn("A"); when(mock.methodWithoutArgs()).thenReturn("B"); expect(mock.methodWithoutArgs(), equals("B")); }); + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); + test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { @@ -237,11 +264,13 @@ void main() { expect(mock.typeParameterizedFn([42], [43]), equals("A lot!")); expect(mock.typeParameterizedFn([43], [44]), equals("A lot!")); }); + test("should mock method with an optional typed arg matcher", () { when(mock.typeParameterizedFn(typed(any), typed(any), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); }); + test( "should mock method with an optional typed arg matcher and an optional real arg", () { @@ -250,6 +279,7 @@ void main() { expect( mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); }); + test("should mock method with only some typed arg matchers", () { when(mock.typeParameterizedFn(typed(any), [43], typed(any))) .thenReturn("A lot!"); @@ -257,18 +287,21 @@ void main() { when(mock.typeParameterizedFn(typed(any), [43])).thenReturn("A bunch!"); expect(mock.typeParameterizedFn([42], [43]), equals("A bunch!")); }); + test("should throw when [typed] used alongside [null].", () { expect(() => when(mock.typeParameterizedFn(typed(any), null, typed(any))), throwsArgumentError); expect(() => when(mock.typeParameterizedFn(typed(any), typed(any), null)), throwsArgumentError); }); + test("should mock method when [typed] used alongside matched [null].", () { when(mock.typeParameterizedFn( typed(any), argThat(equals(null)), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher", () { when(mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any, named: "y"))) @@ -276,6 +309,7 @@ void main() { expect( mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher and an arg matcher", () { when(mock.typeParameterizedNamedFn(typed(any), [43], @@ -284,6 +318,7 @@ void main() { expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher and a regular arg", () { when(mock.typeParameterizedNamedFn(typed(any), [43], @@ -291,12 +326,14 @@ void main() { expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); + test("should throw when [typed] used as a named arg, without `named:`", () { expect( () => when( mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any))), throwsArgumentError); }); + test("should throw when [typed] used as a positional arg, with `named:`", () { expect( @@ -304,6 +341,7 @@ void main() { typed(any), typed(any, named: "y"))), throwsArgumentError); }); + test( "should throw when [typed] used as a named arg, with the wrong `named:`", () { @@ -319,77 +357,85 @@ void main() { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); + test("should verify method with normal args", () { mock.methodWithNormalArgs(42); expectFail( - "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", - () { + "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithNormalArgs(43)); }); 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)", - () { + "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)", - () { + "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})", - () { + "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})", - () { + "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 mock args", () { var m1 = named(new MockedClass(), name: "m1"); mock.methodWithObjArgs(m1); expectFail( - "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)", - () { + "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithObjArgs(new MockedClass())); }); verify(mock.methodWithObjArgs(m1)); }); + test("should mock method with list args", () { mock.methodWithListArgs([42]); expectFail( - "No matching calls. All calls: MockedClass.methodWithListArgs([42])", - () { + "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)", - () { + "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 argument capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([50, 100])); }); + test("should mock method with argument matcher and capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); @@ -404,38 +450,45 @@ void main() { .single, equals(50)); }); + 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)", - () { + "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)", - () { + "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"; - expectFail("No matching calls. All calls: MockedClass.setter==A", () { + expectFail( + "No matching calls. All calls: MockedClass.setter==A\n" + "$noMatchingCallsFooter", () { verify(mock.setter = "B"); }); verify(mock.setter = "A"); }); + test("should verify method with typed arg matchers", () { mock.typeParameterizedFn([42], [43]); verify(mock.typeParameterizedFn(typed(any), typed(any))); }); + test("should verify method with argument capturer", () { mock.typeParameterizedFn([50], [17]); mock.typeParameterizedFn([100], [17]); @@ -447,34 +500,44 @@ void main() { ])); }); }); + group("verify() qualifies", () { group("unqualified as at least one", () { test("zero fails", () { - expectFail("No matching calls.", () { + 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("counts calls", () { - test("zero fails", () { - expectFail("No matching calls.", () { + + 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 passes", () { + + test("one actual call passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()).called(1); }); - test("more than one fails", () { + + test("more than one actual call fails", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", @@ -483,10 +546,60 @@ void main() { }); }); }); + + 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", () { mock.methodWithoutArgs(); expectFail( @@ -495,16 +608,18 @@ void main() { }); }); }); + group("doesn't count already verified again", () { test("fail case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); expectFail( - "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", - () { + "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithoutArgs()); }); }); + test("pass case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); @@ -518,6 +633,7 @@ void main() { test("never touched pass", () { verifyZeroInteractions(mock); }); + test("any touch fails", () { mock.methodWithoutArgs(); expectFail( @@ -526,6 +642,7 @@ void main() { verifyZeroInteractions(mock); }); }); + test("verifired call fails", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); @@ -536,10 +653,12 @@ void main() { }); }); }); + group("verifyNoMoreInteractions()", () { test("never touched pass", () { verifyNoMoreInteractions(mock); }); + test("any unverified touch fails", () { mock.methodWithoutArgs(); expectFail( @@ -548,18 +667,21 @@ void main() { verifyNoMoreInteractions(mock); }); }); + test("verified touch passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); verifyNoMoreInteractions(mock); }); }); + group("verifyInOrder()", () { test("right order passes", () { mock.methodWithoutArgs(); mock.getter; verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); + test("wrong order fails", () { mock.methodWithoutArgs(); mock.getter; @@ -569,6 +691,7 @@ void main() { verifyInOrder([mock.getter, mock.methodWithoutArgs()]); }); }); + test("uncomplete fails", () { mock.methodWithoutArgs(); expectFail( @@ -577,6 +700,7 @@ void main() { verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); }); + test("methods can be called again and again", () { mock.methodWithoutArgs(); mock.getter; @@ -584,6 +708,7 @@ void main() { verifyInOrder( [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); + test("methods can be called again and again - fail case", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); @@ -603,11 +728,13 @@ void main() { expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); }); + test("should captureOut list arguments", () { mock.methodWithListArgs([42]); expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); }); + test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); expect( @@ -615,6 +742,7 @@ void main() { .captured, equals([1, 2])); }); + test("should captureOut with matching arguments", () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); @@ -623,6 +751,7 @@ void main() { .captured, equals([2, 3])); }); + test("should captureOut multiple invocations", () { mock.methodWithNormalArgs(1); mock.methodWithNormalArgs(2);