diff --git a/src/main/java/org/mockito/CheckReturnValue.java b/src/main/java/org/mockito/CheckReturnValue.java index 59bfc333fb..0498c148a8 100644 --- a/src/main/java/org/mockito/CheckReturnValue.java +++ b/src/main/java/org/mockito/CheckReturnValue.java @@ -28,5 +28,5 @@ ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) -@interface CheckReturnValue { +public @interface CheckReturnValue { } diff --git a/src/main/java/org/mockito/MockSettings.java b/src/main/java/org/mockito/MockSettings.java index 82dc0500a5..c79c243175 100644 --- a/src/main/java/org/mockito/MockSettings.java +++ b/src/main/java/org/mockito/MockSettings.java @@ -4,12 +4,15 @@ */ package org.mockito; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; import org.mockito.invocation.InvocationFactory; import org.mockito.invocation.MockHandler; import org.mockito.listeners.InvocationListener; import org.mockito.listeners.VerificationStartedListener; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.SerializableMode; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.io.Serializable; @@ -316,4 +319,18 @@ public interface MockSettings extends Serializable { */ @Incubating MockCreationSettings build(Class typeToMock); + + /** + * Lenient mocks bypass "strict stubbing" validation (see {@link Strictness#STRICT_STUBS}). + * When mock is declared as lenient none of its stubbings will be checked for potential stubbing problems such as + * 'unnecessary stubbing' ({@link UnnecessaryStubbingException}) or for 'stubbing argument mismatch' {@link PotentialStubbingProblem}. + * + *

+     *   Foo mock = mock(Foo.class, withSettings.lenient());
+     * 
+ * + * For more information and an elaborate example, see {@link Mockito#lenient()}. + */ + @Incubating + MockSettings lenient(); } diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index 2a384885e2..fb03b91467 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -4,6 +4,8 @@ */ package org.mockito; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; import org.mockito.internal.InternalMockHandler; import org.mockito.internal.MockitoCore; import org.mockito.internal.creation.MockSettingsImpl; @@ -28,6 +30,7 @@ import org.mockito.session.MockitoSessionLogger; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer1; +import org.mockito.stubbing.LenientStubber; import org.mockito.stubbing.OngoingStubbing; import org.mockito.stubbing.Stubber; import org.mockito.stubbing.Stubbing; @@ -94,12 +97,13 @@ * 37. Java 8 Custom Answer Support (Since 2.1.0)
* 38. Meta data and generic type retention (Since 2.1.0)
* 39. Mocking final types, enums and final methods (Since 2.1.0)
- * 40. (*new*) Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+)
- * 41. (**new**) Advanced public API for framework integrations (Since 2.10.+)
- * 42. (**new**) New API for integrations: listening on verification start events (Since 2.11.+)
- * 43. (**new**) New API for integrations: MockitoSession is usable by testing frameworks (Since 2.15.+)
+ * 40. Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+)
+ * 41. Advanced public API for framework integrations (Since 2.10.+)
+ * 42. New API for integrations: listening on verification start events (Since 2.11.+)
+ * 43. New API for integrations: MockitoSession is usable by testing frameworks (Since 2.15.+)
* 44. Deprecated org.mockito.plugins.InstantiatorProvider as it was leaking internal API. it was replaced by org.mockito.plugins.InstantiatorProvider2 (Since 2.15.4)
- * 45. (**new**) New JUnit Jupiter (JUnit5+) extension
+ * 45. New JUnit Jupiter (JUnit5+) extension
+ * 46. New Mockito.lenient() and MockSettings.lenient() methods (Since 2.20.0
* * *

0. Migrating to Mockito 2

@@ -1366,7 +1370,7 @@ * org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker * *

40. - * (*new*) Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+)

+ * Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+) * * To quickly find out how "stricter" Mockito can make you more productive and get your tests cleaner, see: * * *

43. - * (**new**) New API for integrations: MockitoSession is usable by testing frameworks (Since 2.15.+)

+ * New API for integrations: MockitoSession is usable by testing frameworks (Since 2.15.+) * *

{@link MockitoSessionBuilder} and {@link MockitoSession} were enhanced to enable reuse by testing framework * integrations (e.g. {@link MockitoRule} for JUnit):

@@ -1502,10 +1506,32 @@ * by {@link org.mockito.plugins.InstantiatorProvider2}. Old {@link org.mockito.plugins.InstantiatorProvider * instantiator providers} will continue to work, but it is recommended to switch to the new API.

* - *

45. (**new**) New JUnit Jupiter (JUnit5+) extension

+ *

45. New JUnit Jupiter (JUnit5+) extension

* * For integration with JUnit Jupiter (JUnit5+), use the `org.mockito:mockito-junit-jupiter` artifact. * For more information about the usage of the integration, see the JavaDoc of MockitoExtension. + * + *

46. + * New Mockito.lenient() and MockSettings.lenient() methods (Since 2.20.0)

+ * + * Strict stubbing feature is available since early Mockito 2. + * It is very useful because it drives cleaner tests and improved productivity. + * Strict stubbing reports unnecessary stubs, detects stubbing argument mismatch and makes the tests more DRY ({@link Strictness#STRICT_STUBS}). + * This comes with a trade-off: in some cases, you may get false negatives from strict stubbing. + * To remedy those scenarios you can now configure specific stubbing to be lenient, while all the other stubbings and mocks use strict stubbing: + * + *

+ *   lenient().when(mock.foo()).thenReturn("ok");
+ * 
+ * + * If you want all the stubbings on a given mock to be lenient, you can configure the mock accordingly: + * + *

+ *   Foo mock = Mockito.mock(Foo.class, withSettings().lenient());
+ * 
+ * + * For more information refer to {@link Mockito#lenient()}. + * Let us know how do you find the new feature by opening a GitHub issue to discuss! */ @SuppressWarnings("unchecked") public class Mockito extends ArgumentMatchers { @@ -2950,4 +2976,72 @@ public static MockitoFramework framework() { public static MockitoSessionBuilder mockitoSession() { return new DefaultMockitoSessionBuilder(); } + + /** + * Lenient stubs bypass "strict stubbing" validation (see {@link Strictness#STRICT_STUBS}). + * When stubbing is declared as lenient, it will not be checked for potential stubbing problems such as + * 'unnecessary stubbing' ({@link UnnecessaryStubbingException}) or for 'stubbing argument mismatch' {@link PotentialStubbingProblem}. + * + *

+     *   lenient().when(mock.foo()).thenReturn("ok");
+     * 
+ * + * Most mocks in most tests don't need leniency and should happily prosper with {@link Strictness#STRICT_STUBS}. + *
    + *
  • If a specific stubbing needs to be lenient - use this method
  • + *
  • If a specific mock need to have stubbings lenient - use {@link MockSettings#lenient()}
  • + *
  • If a specific test method / test class needs to have all stubbings lenient + * - configure strictness using our JUnit support ({@link MockitoJUnit} or Mockito Session ({@link MockitoSession})
  • + * + *

    Elaborate example

    + * + * In below example, 'foo.foo()' is a stubbing that was moved to 'before()' method to avoid duplication. + * Doing so makes one of the test methods ('test3()') fail with 'unnecessary stubbing'. + * To resolve it we can configure 'foo.foo()' stubbing in 'before()' method to be lenient. + * Alternatively, we can configure entire 'foo' mock as lenient. + *

    + * This example is simplified and not realistic. + * Pushing stubbings to 'before()' method may cause tests to be less readable. + * Some repetition in tests is OK, use your own judgement to write great tests! + * It is not desired to eliminate all possible duplication from the test code + * because it may add complexity and conceal important test information. + * + *

    
    +     * public class SomeTest {
    +     *
    +     *     @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(STRICT_STUBS);
    +     *
    +     *     @Mock Foo foo;
    +     *     @Mock Bar bar;
    +     *
    +     *     @Before public void before() {
    +     *         when(foo.foo()).thenReturn("ok");
    +     *
    +     *         // it is better to configure the stubbing to be lenient:
    +     *         // lenient().when(foo.foo()).thenReturn("ok");
    +     *
    +     *         // or the entire mock to be lenient:
    +     *         // foo = mock(Foo.class, withSettings().lenient());
    +     *     }
    +     *
    +     *     @Test public void test1() {
    +     *         foo.foo();
    +     *     }
    +     *
    +     *     @Test public void test2() {
    +     *         foo.foo();
    +     *     }
    +     *
    +     *     @Test public void test3() {
    +     *         bar.bar();
    +     *     }
    +     * }
    +     * 
    + * + * @since 2.20.0 + */ + @Incubating + public static LenientStubber lenient() { + return MOCKITO_CORE.lenient(); + } } diff --git a/src/main/java/org/mockito/exceptions/misusing/PotentialStubbingProblem.java b/src/main/java/org/mockito/exceptions/misusing/PotentialStubbingProblem.java index 42e990dab8..5eb6a77ae8 100644 --- a/src/main/java/org/mockito/exceptions/misusing/PotentialStubbingProblem.java +++ b/src/main/java/org/mockito/exceptions/misusing/PotentialStubbingProblem.java @@ -5,8 +5,8 @@ package org.mockito.exceptions.misusing; import org.mockito.Mockito; -import org.mockito.quality.Strictness; import org.mockito.exceptions.base.MockitoException; +import org.mockito.quality.Strictness; /** * {@code PotentialStubbingProblem} improves productivity by failing the test early when the user @@ -53,23 +53,7 @@ * It is a well known limitation of Mockito API and another example how Mockito optimizes its clean API for 95% of the cases * while still supporting edge cases. * - *
  • Reduce the strictness level in the test method (only for JUnit Rules): - *
    
    - * public class ExampleTest {
    - *     @Rule
    - *     public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
    - *
    - *     @Test public void exampleTest() {
    - *         //Change the strictness level only for this test method:
    - *         mockito.strictness(Strictness.LENIENT);
    - *
    - *         //remaining test code
    - *     }
    - * }
    - * 
    - * Currently, reducing strictness is only available to JUnit rules. - * If you need it in a different context let us know at issue 857. - *
  • + *
  • Reduce the strictness level per stubbing, per mock or per test - see {@link Mockito#lenient()}
  • *
  • To opt-out in Mockito 2.x, simply remove the strict stubbing setting in the test class.
  • * *

    diff --git a/src/main/java/org/mockito/exceptions/misusing/UnnecessaryStubbingException.java b/src/main/java/org/mockito/exceptions/misusing/UnnecessaryStubbingException.java index c8713b3776..2d4605b4f6 100644 --- a/src/main/java/org/mockito/exceptions/misusing/UnnecessaryStubbingException.java +++ b/src/main/java/org/mockito/exceptions/misusing/UnnecessaryStubbingException.java @@ -4,23 +4,22 @@ */ package org.mockito.exceptions.misusing; +import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.exceptions.base.MockitoException; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.junit.MockitoRule; -import org.mockito.quality.MockitoHint; -import org.mockito.quality.Strictness; /** * This exception indicates presence of unused stubbings. * It is highly recommended to remove unused stubbings to keep the codebase clean. - * You can opt-out from detecting unused stubbings by configuring: - *

      - *
    • JUnit rule - {@link MockitoRule#strictness(Strictness)} or {@link MockitoRule#silent()}
    • - *
    • JUnit runner - {@link MockitoJUnitRunner.Silent}
    • - *
    • Mockito session - {@link MockitoSession}
    • - *
    - * For more information about detecting unused stubbings, see {@link MockitoHint}. + * In a rare scenario that unused stubbing is a false negative you can opt out from the validation via + * (in order of ascending scope): + *
      + *
    1. Declaring specific stubbing as 'lenient' - {@link Mockito#lenient()}
    2. + *
    3. Declaring specific mock as 'lenient' - {@link org.mockito.MockSettings#lenient()}
    4. + *
    5. Declaring all mocks in given test class or test method mock as 'lenient' with + * our JUnit support ({@link org.mockito.junit.MockitoJUnit}) or Mockito session ({@link MockitoSession})
    6. + *
    + * *

    * Unnecessary stubbings are stubbed method calls that were never realized during test execution. Example: *

    
    @@ -45,11 +44,6 @@
      * Mockito JUnit Runner triggers UnnecessaryStubbingException only when none of the test methods use the stubbings.
      * This means that it is ok to put default stubbing in a 'setup' method or in test class constructor.
      * That default stubbing needs to be used at least once by one of the test methods.
    - * 

    - * To find out more about detecting unused stubbings see {@link MockitoHint}. - * See javadoc for {@link MockitoRule} to understand the behavior or Mockito JUnit Rules. - * See javadoc for {@link MockitoJUnitRunner} to find out how Mockito JUnit Runner detects unused stubs. - * See javadoc for {@link MockitoSession} to find out about detecting unused stubs without JUnit. */ public class UnnecessaryStubbingException extends MockitoException { public UnnecessaryStubbingException(String message) { diff --git a/src/main/java/org/mockito/internal/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java index 419d228436..c764e59825 100644 --- a/src/main/java/org/mockito/internal/MockitoCore.java +++ b/src/main/java/org/mockito/internal/MockitoCore.java @@ -12,6 +12,7 @@ import org.mockito.internal.invocation.finder.VerifiableInvocationsFinder; import org.mockito.internal.listeners.VerificationStartedNotifier; import org.mockito.internal.progress.MockingProgress; +import org.mockito.internal.stubbing.DefaultLenientStubber; import org.mockito.internal.stubbing.InvocationContainerImpl; import org.mockito.internal.stubbing.OngoingStubbingImpl; import org.mockito.internal.stubbing.StubberImpl; @@ -25,6 +26,8 @@ import org.mockito.invocation.Invocation; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.LenientStubber; import org.mockito.stubbing.OngoingStubbing; import org.mockito.stubbing.Stubber; import org.mockito.verification.VerificationMode; @@ -165,10 +168,14 @@ public InOrder inOrder(Object... mocks) { } public Stubber stubber() { + return stubber(null); + } + + public Stubber stubber(Strictness strictness) { MockingProgress mockingProgress = mockingProgress(); mockingProgress.stubbingStarted(); mockingProgress.resetOngoingStubbing(); - return new StubberImpl(); + return new StubberImpl(strictness); } public void validateMockitoUsage() { @@ -202,4 +209,8 @@ public Object[] ignoreStubs(Object... mocks) { public MockingDetails mockingDetails(Object toInspect) { return new DefaultMockingDetails(toInspect); } + + public LenientStubber lenient() { + return new DefaultLenientStubber(); + } } diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java index 82af98aeb8..ca67730793 100644 --- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java +++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java @@ -226,6 +226,12 @@ public MockCreationSettings build(Class typeToMock) { return validatedSettings(typeToMock, (CreationSettings) this); } + @Override + public MockSettings lenient() { + this.lenient = true; + return this; + } + private static CreationSettings validatedSettings(Class typeToMock, CreationSettings source) { MockCreationValidator validator = new MockCreationValidator(); diff --git a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java index 4befa99824..03afd80947 100644 --- a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java +++ b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java @@ -37,6 +37,7 @@ public class CreationSettings implements MockCreationSettings, Serializabl private boolean useConstructor; private Object outerClassInstance; private Object[] constructorArgs; + protected boolean lenient; public CreationSettings() {} @@ -55,6 +56,7 @@ public CreationSettings(CreationSettings copy) { this.useConstructor = copy.isUsingConstructor(); this.outerClassInstance = copy.getOuterClassInstance(); this.constructorArgs = copy.getConstructorArgs(); + this.lenient = copy.lenient; } @Override @@ -153,4 +155,9 @@ public Object getOuterClassInstance() { public boolean isStubOnly() { return stubOnly; } + + @Override + public boolean isLenient() { + return lenient; + } } diff --git a/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java b/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java index 57a9d435b0..bd50535ade 100644 --- a/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java +++ b/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java @@ -18,8 +18,10 @@ import org.mockito.invocation.InvocationContainer; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; +import org.mockito.stubbing.Stubbing; import org.mockito.verification.VerificationMode; +import java.util.Collection; import java.util.List; import static org.mockito.internal.exceptions.Reporter.stubPassedToVerify; @@ -130,11 +132,12 @@ private VerificationDataImpl createVerificationData(InvocationContainerImpl invo return new VerificationDataImpl(invocationContainer, invocationMatcher); } - private void notifyStubbedAnswerLookup(Invocation invocation, StubbedInvocationMatcher exception) { + private void notifyStubbedAnswerLookup(Invocation invocation, Stubbing stubbingFound) { //TODO #793 - when completed, we should be able to get rid of the casting below - List listeners = ((CreationSettings) mockSettings).getStubbingLookupListeners(); + List listeners = ((CreationSettings) this.mockSettings).getStubbingLookupListeners(); for (StubbingLookupListener listener : listeners) { - listener.onStubbingLookup(invocation, exception); + Collection stubbings = this.invocationContainer.getStubbingsAscending(); + listener.onStubbingLookup(invocation, stubbingFound, stubbings, this.mockSettings); } } } diff --git a/src/main/java/org/mockito/internal/invocation/UnusedStubsFinder.java b/src/main/java/org/mockito/internal/invocation/UnusedStubsFinder.java index 0f9ac6dc0f..5aec36dc13 100644 --- a/src/main/java/org/mockito/internal/invocation/UnusedStubsFinder.java +++ b/src/main/java/org/mockito/internal/invocation/UnusedStubsFinder.java @@ -23,7 +23,7 @@ public class UnusedStubsFinder { public List find(List mocks) { List unused = new LinkedList(); for (Object mock : mocks) { - List fromSingleMock = MockUtil.getInvocationContainer(mock).getStubbedInvocations(); + List fromSingleMock = MockUtil.getInvocationContainer(mock).getStubbingsDescending(); for(Stubbing s : fromSingleMock) { if (!s.wasUsed()) { unused.add(s.getInvocation()); diff --git a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java index c7e2551e90..5c99068ef7 100644 --- a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java +++ b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java @@ -6,8 +6,9 @@ import org.mockito.internal.exceptions.Reporter; import org.mockito.internal.listeners.StubbingLookupListener; +import org.mockito.internal.stubbing.UnusedStubbingReporting; import org.mockito.invocation.Invocation; -import org.mockito.invocation.MatchableInvocation; +import org.mockito.mock.MockCreationSettings; import org.mockito.quality.Strictness; import org.mockito.stubbing.Stubbing; @@ -15,7 +16,7 @@ import java.util.LinkedList; import java.util.List; -import static org.mockito.Mockito.mockingDetails; +import static org.mockito.internal.stubbing.StrictnessSelector.determineStrictness; /** * Default implementation of stubbing lookup listener. @@ -30,15 +31,17 @@ class DefaultStubbingLookupListener implements StubbingLookupListener { this.currentStrictness = strictness; } - public void onStubbingLookup(Invocation invocation, MatchableInvocation stubbingFound) { - if (currentStrictness != Strictness.STRICT_STUBS) { + public void onStubbingLookup(Invocation invocation, Stubbing stubbingFound, Collection allStubbings, MockCreationSettings mockSettings) { + Strictness actualStrictness = determineStrictness(stubbingFound, mockSettings, currentStrictness); + + if (actualStrictness != Strictness.STRICT_STUBS) { return; } if (stubbingFound == null) { //If stubbing was not found for invocation it means that either the mock invocation was not stubbed or //we have a stubbing arg mismatch. - List argMismatchStubbings = potentialArgMismatches(invocation); + List argMismatchStubbings = potentialArgMismatches(invocation, allStubbings); if (!argMismatchStubbings.isEmpty()) { mismatchesReported = true; Reporter.potentialStubbingProblem(invocation, argMismatchStubbings); @@ -50,11 +53,11 @@ public void onStubbingLookup(Invocation invocation, MatchableInvocation stubbing } } - private static List potentialArgMismatches(Invocation invocation) { + private static List potentialArgMismatches(Invocation invocation, Collection stubbings) { List matchingStubbings = new LinkedList(); - Collection stubbings = mockingDetails(invocation.getMock()).getStubbings(); for (Stubbing s : stubbings) { - if (!s.wasUsed() && s.getInvocation().getMethod().getName().equals(invocation.getMethod().getName())) { + if (UnusedStubbingReporting.shouldBeReported(s) + && s.getInvocation().getMethod().getName().equals(invocation.getMethod().getName())) { matchingStubbings.add(s.getInvocation()); } } diff --git a/src/main/java/org/mockito/internal/junit/UnusedStubbings.java b/src/main/java/org/mockito/internal/junit/UnusedStubbings.java index 5b6434d840..3505d2cb1c 100644 --- a/src/main/java/org/mockito/internal/junit/UnusedStubbings.java +++ b/src/main/java/org/mockito/internal/junit/UnusedStubbings.java @@ -6,11 +6,11 @@ import org.mockito.internal.exceptions.Reporter; import org.mockito.internal.util.MockitoLogger; -import org.mockito.internal.util.collections.ListUtil; import org.mockito.invocation.Invocation; import org.mockito.stubbing.Stubbing; import java.util.Collection; +import java.util.LinkedList; import java.util.List; /** @@ -47,16 +47,19 @@ public String toString() { return unused.toString(); } - public void reportUnused() { - if (!unused.isEmpty()) { - List invocations = ListUtil.convert(unused, (ListUtil.Converter) new ListUtil.Converter() { - public Invocation convert(Stubbing s) { - return s.getInvocation(); - } - }); - + void reportUnused() { + if (unused.isEmpty()) { + return; + } - Reporter.unncessaryStubbingException(invocations); + List invocations = new LinkedList(); + for (Stubbing stubbing : unused) { + invocations.add(stubbing.getInvocation()); } + if (invocations.isEmpty()) { + return; + } + + Reporter.unncessaryStubbingException(invocations); } } diff --git a/src/main/java/org/mockito/internal/junit/UnusedStubbingsFinder.java b/src/main/java/org/mockito/internal/junit/UnusedStubbingsFinder.java index 715c39326a..14c61c5578 100644 --- a/src/main/java/org/mockito/internal/junit/UnusedStubbingsFinder.java +++ b/src/main/java/org/mockito/internal/junit/UnusedStubbingsFinder.java @@ -4,12 +4,18 @@ */ package org.mockito.internal.junit; -import org.mockito.stubbing.Stubbing; import org.mockito.internal.invocation.finder.AllInvocationsFinder; +import org.mockito.internal.stubbing.UnusedStubbingReporting; import org.mockito.internal.util.collections.ListUtil.Filter; import org.mockito.invocation.Invocation; +import org.mockito.stubbing.Stubbing; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.mockito.internal.util.collections.ListUtil.filter; @@ -19,14 +25,15 @@ public class UnusedStubbingsFinder { /** - * Gets all unused stubbings for given set of mock objects, in order + * Gets all unused stubbings for given set of mock objects, in order. + * Stubbings explicitily marked as LENIENT are not included. */ public UnusedStubbings getUnusedStubbings(Iterable mocks) { Set stubbings = AllInvocationsFinder.findStubbings(mocks); List unused = filter(stubbings, new Filter() { public boolean isOut(Stubbing s) { - return s.wasUsed(); + return !UnusedStubbingReporting.shouldBeReported(s); } }); @@ -50,7 +57,7 @@ public Collection getUnusedStubbingsByLocation(Iterable mock //note that those are _not_ locations where the stubbings was used Set locationsOfUsedStubbings = new HashSet(); for (Stubbing s : stubbings) { - if (s.wasUsed()) { + if (!UnusedStubbingReporting.shouldBeReported(s)) { String location = s.getInvocation().getLocation().toString(); locationsOfUsedStubbings.add(location); } diff --git a/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java b/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java index 81cf537776..fa7daf3914 100644 --- a/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java +++ b/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java @@ -5,7 +5,10 @@ package org.mockito.internal.listeners; import org.mockito.invocation.Invocation; -import org.mockito.invocation.MatchableInvocation; +import org.mockito.mock.MockCreationSettings; +import org.mockito.stubbing.Stubbing; + +import java.util.Collection; /** * Listens to attempts to look up stubbing answer for given mocks. This class is internal for now. @@ -27,11 +30,13 @@ public interface StubbingLookupListener { /** * Called by the framework when Mockito looked up an answer for invocation on a mock. * - * TODO when making this public, we should have an event object instead of 2 arguments in the listener. + * TODO when making this public, we should have an event object instead of multiple arguments in the listener. * - * @param invocation the invocation on the mock + * @param invocation - the invocation that causes stubbing lookup * @param stubbingFound - can be null - it indicates that the invocation was not stubbed. + * @param allStubbings - all stubbings declared on the mock object that we are invoking. + * @param mockSettings - settings of the mock object that we are invoking */ - void onStubbingLookup(Invocation invocation, MatchableInvocation stubbingFound); + void onStubbingLookup(Invocation invocation, Stubbing stubbingFound, Collection allStubbings, MockCreationSettings mockSettings); } diff --git a/src/main/java/org/mockito/internal/stubbing/DefaultLenientStubber.java b/src/main/java/org/mockito/internal/stubbing/DefaultLenientStubber.java new file mode 100644 index 0000000000..0f3fe073a1 --- /dev/null +++ b/src/main/java/org/mockito/internal/stubbing/DefaultLenientStubber.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing; + +import org.mockito.internal.MockitoCore; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; +import org.mockito.stubbing.LenientStubber; +import org.mockito.stubbing.OngoingStubbing; +import org.mockito.stubbing.Stubber; + +public class DefaultLenientStubber implements LenientStubber { + + private final static MockitoCore MOCKITO_CORE = new MockitoCore(); + + @Override + public Stubber doThrow(Throwable... toBeThrown) { + return stubber().doThrow(toBeThrown); + } + + @Override + public Stubber doThrow(Class toBeThrown) { + return stubber().doThrow(toBeThrown); + } + + @Override + public Stubber doThrow(Class toBeThrown, Class... nextToBeThrown) { + return stubber().doThrow(toBeThrown, nextToBeThrown); + } + + @Override + public Stubber doAnswer(Answer answer) { + return stubber().doAnswer(answer); + } + + @Override + public Stubber doNothing() { + return stubber().doNothing(); + } + + @Override + public Stubber doReturn(Object toBeReturned) { + return stubber().doReturn(toBeReturned); + } + + @Override + public Stubber doReturn(Object toBeReturned, Object... nextToBeReturned) { + return stubber().doReturn(toBeReturned, nextToBeReturned); + } + + @Override + public Stubber doCallRealMethod() { + return stubber().doCallRealMethod(); + } + + @Override + public OngoingStubbing when(T methodCall) { + OngoingStubbingImpl ongoingStubbing = (OngoingStubbingImpl) MOCKITO_CORE.when(methodCall); + ongoingStubbing.setStrictness(Strictness.LENIENT); + return ongoingStubbing; + } + + private static Stubber stubber() { + return MOCKITO_CORE.stubber(Strictness.LENIENT); + } +} diff --git a/src/main/java/org/mockito/internal/stubbing/DoAnswerStyleStubbing.java b/src/main/java/org/mockito/internal/stubbing/DoAnswerStyleStubbing.java new file mode 100644 index 0000000000..56491b8577 --- /dev/null +++ b/src/main/java/org/mockito/internal/stubbing/DoAnswerStyleStubbing.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing; + +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Holds answers declared using 'doAnswer' stubbing style. + */ +class DoAnswerStyleStubbing implements Serializable { + + private final List> answers = new ArrayList>(); + private Strictness stubbingStrictness; + + void setAnswers(List> answers, Strictness stubbingStrictness) { + this.stubbingStrictness = stubbingStrictness; + this.answers.addAll(answers); + } + + boolean isSet() { + return answers.isEmpty(); + } + + void clear() { + answers.clear(); + stubbingStrictness = null; + } + + List> getAnswers() { + return answers; + } + + Strictness getStubbingStrictness() { + return stubbingStrictness; + } +} diff --git a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java index 120320be1d..6c98f37c9f 100644 --- a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java @@ -12,12 +12,14 @@ import org.mockito.invocation.InvocationContainer; import org.mockito.invocation.MatchableInvocation; import org.mockito.mock.MockCreationSettings; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Stubbing; import org.mockito.stubbing.ValidableAnswer; import java.io.Serializable; -import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -28,13 +30,16 @@ public class InvocationContainerImpl implements InvocationContainer, Serializabl private static final long serialVersionUID = -5334301962749537177L; private final LinkedList stubbed = new LinkedList(); - private final List> answersForStubbing = new ArrayList>(); + private final DoAnswerStyleStubbing doAnswerStyleStubbing; private final RegisteredInvocations registeredInvocations; + private final Strictness mockStrictness; private MatchableInvocation invocationForStubbing; public InvocationContainerImpl(MockCreationSettings mockSettings) { this.registeredInvocations = createRegisteredInvocations(mockSettings); + this.mockStrictness = mockSettings.isLenient() ? Strictness.LENIENT : null; + this.doAnswerStyleStubbing = new DoAnswerStyleStubbing(); } public void setInvocationForPotentialStubbing(MatchableInvocation invocation) { @@ -46,19 +51,19 @@ public void resetInvocationForPotentialStubbing(MatchableInvocation invocationMa this.invocationForStubbing = invocationMatcher; } - public void addAnswer(Answer answer) { + public void addAnswer(Answer answer, Strictness stubbingStrictness) { registeredInvocations.removeLast(); - addAnswer(answer, false); + addAnswer(answer, false, stubbingStrictness); } public void addConsecutiveAnswer(Answer answer) { - addAnswer(answer, true); + addAnswer(answer, true, null); } /** * Adds new stubbed answer and returns the invocation matcher the answer was added to. */ - public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive) { + public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive, Strictness stubbingStrictness) { Invocation invocation = invocationForStubbing.getInvocation(); mockingProgress().stubbingCompleted(); if (answer instanceof ValidableAnswer) { @@ -69,7 +74,8 @@ public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive) if (isConsecutive) { stubbed.getFirst().addAnswer(answer); } else { - stubbed.addFirst(new StubbedInvocationMatcher(invocationForStubbing, answer)); + Strictness effectiveStrictness = stubbingStrictness != null ? stubbingStrictness : this.mockStrictness; + stubbed.addFirst(new StubbedInvocationMatcher(answer, invocationForStubbing, effectiveStrictness)); } return stubbed.getFirst(); } @@ -84,6 +90,7 @@ public StubbedInvocationMatcher findAnswerFor(Invocation invocation) { for (StubbedInvocationMatcher s : stubbed) { if (s.matches(invocation)) { s.markStubUsed(invocation); + //TODO we should mark stubbed at the point of stubbing, not at the point where the stub is being used invocation.markStubbed(new StubInfoImpl(s)); return s; } @@ -93,12 +100,15 @@ public StubbedInvocationMatcher findAnswerFor(Invocation invocation) { return null; } - public void setAnswersForStubbing(List> answers) { - answersForStubbing.addAll(answers); + /** + * Sets the answers declared with 'doAnswer' style. + */ + public void setAnswersForStubbing(List> answers, Strictness strictness) { + doAnswerStyleStubbing.setAnswers(answers, strictness); } public boolean hasAnswersForStubbing() { - return !answersForStubbing.isEmpty(); + return !doAnswerStyleStubbing.isSet(); } public boolean hasInvocationForPotentialStubbing() { @@ -108,10 +118,10 @@ public boolean hasInvocationForPotentialStubbing() { public void setMethodForStubbing(MatchableInvocation invocation) { invocationForStubbing = invocation; assert hasAnswersForStubbing(); - for (int i = 0; i < answersForStubbing.size(); i++) { - addAnswer(answersForStubbing.get(i), i != 0); + for (int i = 0; i < doAnswerStyleStubbing.getAnswers().size(); i++) { + addAnswer(doAnswerStyleStubbing.getAnswers().get(i), i != 0, doAnswerStyleStubbing.getStubbingStrictness()); } - answersForStubbing.clear(); + doAnswerStyleStubbing.clear(); } @Override @@ -127,10 +137,22 @@ public void clearInvocations() { registeredInvocations.clear(); } - public List getStubbedInvocations() { + /** + * Stubbings in descending order, most recent first + */ + public List getStubbingsDescending() { return (List) stubbed; } + /** + * Stubbings in ascending order, most recent last + */ + public Collection getStubbingsAscending() { + List result = new LinkedList(stubbed); + Collections.reverse(result); + return result; + } + public Object invokedMock() { return invocationForStubbing.getInvocation().getMock(); } diff --git a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java index 78a10270a3..cd27c392dd 100644 --- a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java @@ -5,16 +5,18 @@ package org.mockito.internal.stubbing; import org.mockito.invocation.Invocation; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import org.mockito.stubbing.OngoingStubbing; -import static org.mockito.internal.exceptions.Reporter.incorrectUseOfApi; - import java.util.List; +import static org.mockito.internal.exceptions.Reporter.incorrectUseOfApi; + public class OngoingStubbingImpl extends BaseStubbing { private final InvocationContainerImpl invocationContainer; + private Strictness strictness; public OngoingStubbingImpl(InvocationContainerImpl invocationContainer) { this.invocationContainer = invocationContainer; @@ -26,7 +28,7 @@ public OngoingStubbing thenAnswer(Answer answer) { throw incorrectUseOfApi(); } - invocationContainer.addAnswer(answer); + invocationContainer.addAnswer(answer, strictness); return new ConsecutiveStubbing(invocationContainer); } @@ -45,6 +47,10 @@ public List getRegisteredInvocations() { public M getMock() { return (M) invocationContainer.invokedMock(); } + + public void setStrictness(Strictness strictness) { + this.strictness = strictness; + } } diff --git a/src/main/java/org/mockito/internal/stubbing/StrictnessSelector.java b/src/main/java/org/mockito/internal/stubbing/StrictnessSelector.java new file mode 100644 index 0000000000..6009e9340e --- /dev/null +++ b/src/main/java/org/mockito/internal/stubbing/StrictnessSelector.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing; + +import org.mockito.mock.MockCreationSettings; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Stubbing; + +/** + * Helps determining the actual strictness given that it can be configured in multiple ways (at mock, at stubbing, in rule) + */ +public class StrictnessSelector { + + /** + * Determines the actual strictness in the following importance order: + * 1st - strictness configured when declaring stubbing; + * 2nd - strictness configured at mock level; + * 3rd - strictness configured at test level (rule, mockito session) + * + * @param stubbing stubbing to check for strictness. Null permitted. + * @param mockSettings settings of the mock object, may or may not have strictness configured. Must not be null. + * @param testLevelStrictness strictness configured using the test-level configuration (rule, mockito session). Null permitted. + * + * @return actual strictness, can be null. + */ + public static Strictness determineStrictness(Stubbing stubbing, MockCreationSettings mockSettings, Strictness testLevelStrictness) { + if (stubbing != null && stubbing.getStrictness() != null) { + return stubbing.getStrictness(); + } + + if (mockSettings.isLenient()) { + return Strictness.LENIENT; + } + + return testLevelStrictness; + } +} diff --git a/src/main/java/org/mockito/internal/stubbing/StubbedInvocationMatcher.java b/src/main/java/org/mockito/internal/stubbing/StubbedInvocationMatcher.java index 45182105c2..e7ccac4d11 100644 --- a/src/main/java/org/mockito/internal/stubbing/StubbedInvocationMatcher.java +++ b/src/main/java/org/mockito/internal/stubbing/StubbedInvocationMatcher.java @@ -5,11 +5,12 @@ package org.mockito.internal.stubbing; import org.mockito.internal.invocation.InvocationMatcher; -import org.mockito.invocation.MatchableInvocation; -import org.mockito.stubbing.Stubbing; import org.mockito.invocation.DescribedInvocation; import org.mockito.invocation.InvocationOnMock; +import org.mockito.invocation.MatchableInvocation; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; +import org.mockito.stubbing.Stubbing; import java.io.Serializable; import java.util.Queue; @@ -20,10 +21,12 @@ public class StubbedInvocationMatcher extends InvocationMatcher implements Seria private static final long serialVersionUID = 4919105134123672727L; private final Queue answers = new ConcurrentLinkedQueue(); + private final Strictness strictness; private DescribedInvocation usedAt; - public StubbedInvocationMatcher(MatchableInvocation invocation, Answer answer) { + public StubbedInvocationMatcher(Answer answer, MatchableInvocation invocation, Strictness strictness) { super(invocation.getInvocation(), invocation.getMatchers()); + this.strictness = strictness; this.answers.add(answer); } @@ -52,4 +55,9 @@ public boolean wasUsed() { public String toString() { return super.toString() + " stubbed with: " + answers; } + + @Override + public Strictness getStrictness() { + return strictness; + } } diff --git a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java index 7771c0438f..2172809e36 100644 --- a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java @@ -8,6 +8,7 @@ import org.mockito.internal.stubbing.answers.Returns; import org.mockito.internal.stubbing.answers.ThrowsException; import org.mockito.internal.util.MockUtil; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Stubber; @@ -24,6 +25,12 @@ public class StubberImpl implements Stubber { + private final Strictness strictness; + + public StubberImpl(Strictness strictness) { + this.strictness = strictness; + } + private final List> answers = new LinkedList>(); @Override @@ -36,7 +43,7 @@ public T when(T mock) { throw notAMockPassedToWhenMethod(); } - MockUtil.getInvocationContainer(mock).setAnswersForStubbing(answers); + MockUtil.getInvocationContainer(mock).setAnswersForStubbing(answers, strictness); return mock; } diff --git a/src/main/java/org/mockito/internal/stubbing/UnusedStubbingReporting.java b/src/main/java/org/mockito/internal/stubbing/UnusedStubbingReporting.java new file mode 100644 index 0000000000..6a96592453 --- /dev/null +++ b/src/main/java/org/mockito/internal/stubbing/UnusedStubbingReporting.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing; + +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Stubbing; + +/** + * Helps determining if stubbing should be reported as unused + */ +public class UnusedStubbingReporting { + + /** + * Decides if the stubbing should be reported as unused. + * Lenient stubbings are not reported as unused. + */ + public static boolean shouldBeReported(Stubbing stubbing) { + return !stubbing.wasUsed() && stubbing.getStrictness() != Strictness.LENIENT; + } +} diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java index 3b2cb3dabf..3909ff041c 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java @@ -61,7 +61,7 @@ private Object deepStub(InvocationOnMock invocation, GenericMetadataSupport retu // matches invocation for verification // TODO why don't we do container.findAnswer here? - for (Stubbing stubbing : container.getStubbedInvocations()) { + for (Stubbing stubbing : container.getStubbingsDescending()) { if (container.getInvocationForStubbing().matches(stubbing.getInvocation())) { return stubbing.answer(invocation); } @@ -118,7 +118,7 @@ private ReturnsDeepStubs returnsDeepStubsAnswerUsing(final GenericMetadataSuppor private StubbedInvocationMatcher recordDeepStubAnswer(final Object mock, InvocationContainerImpl container) { DeeplyStubbedAnswer answer = new DeeplyStubbedAnswer(mock); - return container.addAnswer(answer, false); + return container.addAnswer(answer, false, null); } protected GenericMetadataSupport actualParameterizedType(Object mock) { diff --git a/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java b/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java index 0c3669c9dc..4f739cdbc7 100644 --- a/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java +++ b/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java @@ -8,15 +8,12 @@ import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.internal.debugging.InvocationsPrinter; import org.mockito.internal.stubbing.InvocationContainerImpl; -import org.mockito.internal.stubbing.StubbingComparator; import org.mockito.invocation.Invocation; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Stubbing; import java.util.Collection; -import java.util.List; -import java.util.TreeSet; /** * Class to inspect any object, and identify whether a particular object is either a mock or a spy. This is @@ -57,10 +54,7 @@ public MockCreationSettings getMockCreationSettings() { @Override public Collection getStubbings() { - List stubbings = getInvocationContainer().getStubbedInvocations(); - TreeSet out = new TreeSet(new StubbingComparator()); - out.addAll(stubbings); - return out; + return getInvocationContainer().getStubbingsAscending(); } @Override diff --git a/src/main/java/org/mockito/junit/MockitoJUnit.java b/src/main/java/org/mockito/junit/MockitoJUnit.java index 2f8b65f3bf..da9428ba41 100644 --- a/src/main/java/org/mockito/junit/MockitoJUnit.java +++ b/src/main/java/org/mockito/junit/MockitoJUnit.java @@ -5,13 +5,18 @@ package org.mockito.junit; import org.mockito.Incubating; -import org.mockito.quality.Strictness; import org.mockito.internal.junit.JUnitRule; import org.mockito.internal.junit.VerificationCollectorImpl; import org.mockito.internal.util.ConsoleMockitoLogger; +import org.mockito.quality.Strictness; /** - * The JUnit rule can be used instead of {@link MockitoJUnitRunner}. See {@link MockitoRule}. + * Mockito supports JUnit via: + *
  • + *
      JUnit Rules - see {@link MockitoRule}
    + *
      JUnit runners - see {@link MockitoJUnitRunner}
    + * + *
  • * * @since 1.10.17 */ diff --git a/src/main/java/org/mockito/junit/MockitoRule.java b/src/main/java/org/mockito/junit/MockitoRule.java index 296faa7149..561fff721b 100644 --- a/src/main/java/org/mockito/junit/MockitoRule.java +++ b/src/main/java/org/mockito/junit/MockitoRule.java @@ -6,6 +6,8 @@ import org.junit.rules.MethodRule; import org.mockito.Incubating; +import org.mockito.MockSettings; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.MockitoHint; @@ -104,7 +106,7 @@ public interface MockitoRule extends MethodRule { * The strictness, especially "strict stubs" ({@link Strictness#STRICT_STUBS}) * helps debugging and keeping tests clean. * It's a new feature introduced in Mockito 2.3. - * Other levels of strictness - "Warn" - current default ({@link Strictness#WARN}) + * Other levels of strictness - "warn" - ({@link Strictness#WARN}) * and "lenient" ({@link MockitoRule#silent()}) strictness were already present in Mockito 2.1.0. * Version 2.3.0 introduces "strict stubs" ({@link Strictness#STRICT_STUBS}). * @@ -115,21 +117,12 @@ public interface MockitoRule extends MethodRule { * } * * - * How strictness level influences the behavior of JUnit rule: - *
      - *
    1. {@link Strictness#LENIENT} - equivalent of {@link MockitoRule#silent()} - - * no added behavior. The default of Mockito 1.x
    2. - *
    3. {@link Strictness#WARN} - helps keeping tests clean and with debuggability. - * Reports console warnings about unused stubs - * and stubbing argument mismatches (see {@link MockitoHint}). - * The default of Mockito 2.x
    4. - *
    5. {@link Strictness#STRICT_STUBS} - ensures clean tests, - * reduces test code duplication, improves debuggability. - * See the details in the Javadoc for {@link Strictness#STRICT_STUBS}. - *
    - * + * See Javadoc for {@link Strictness} to learn how strictness influences the behavior of the JUnit rule. + * See {@link Strictness#STRICT_STUBS} to learn why is it recommended to use "strict stubbing". + *

    * It is possible to tweak the strictness per test method. * Why would you need it? See the use cases in Javadoc for {@link PotentialStubbingProblem} class. + * In order to tweak strictness per stubbing see {@link Mockito#lenient()}, per mock see {@link MockSettings#lenient()}. * *

    
          * public class ExampleTest {
    @@ -146,7 +139,7 @@ public interface MockitoRule extends MethodRule {
          * }
          * 
    * - * "Strict stubs" are tentatively planned to be the default for Mockito v3 + * "Strict stubs" are planned to be the default for Mockito v3 * We are very eager to hear feedback about "strict stubbing" feature, let us know by commenting on GitHub * issue 769. * Strict stubbing is an attempt to improve testability and productivity with Mockito. Tell us what you think! diff --git a/src/main/java/org/mockito/mock/MockCreationSettings.java b/src/main/java/org/mockito/mock/MockCreationSettings.java index b58daf0efa..7e74be8a90 100644 --- a/src/main/java/org/mockito/mock/MockCreationSettings.java +++ b/src/main/java/org/mockito/mock/MockCreationSettings.java @@ -6,9 +6,11 @@ package org.mockito.mock; import org.mockito.Incubating; +import org.mockito.MockSettings; import org.mockito.NotExtensible; import org.mockito.listeners.InvocationListener; import org.mockito.listeners.VerificationStartedListener; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.List; @@ -109,4 +111,13 @@ public interface MockCreationSettings { */ @Incubating Object getOuterClassInstance(); + + /** + * Informs if the mock was created with "lenient" strictness, e.g. having {@link Strictness#LENIENT} characteristic. + * For more information about using mocks with lenient strictness, see {@link MockSettings#lenient()}. + * + * @since 2.20.0 + */ + @Incubating + boolean isLenient(); } diff --git a/src/main/java/org/mockito/quality/MockitoHint.java b/src/main/java/org/mockito/quality/MockitoHint.java index d752159aee..f261ac1630 100644 --- a/src/main/java/org/mockito/quality/MockitoHint.java +++ b/src/main/java/org/mockito/quality/MockitoHint.java @@ -8,10 +8,13 @@ import org.mockito.junit.MockitoJUnitRunner; /** + * Stubbing hints were introduced in Mockito 2 in order to improve debuggability while keeping backwards compatibility. + * As Mockito 2 evolved, hints are replaced by "strict stubbing" API ({@link Strictness}). + * In Mockito 3 we won't be needing hints because {@link Strictness#STRICT_STUBS} will be the default for all mocks. + *

    + * Why hints? * To improve productivity when writing Java tests * stubbing hints and warnings are printed to standard output. - * See also "strict stubbing" API ({@link Strictness}) - * which drives cleaner tests and productivity more effectively than Mockito hints. *

    * Hints contain clickable links that take you right to the line of code that contains a possible problem. * Those are hints - they not necessarily indicate real problems 100% of the time. diff --git a/src/main/java/org/mockito/quality/Strictness.java b/src/main/java/org/mockito/quality/Strictness.java index c9da8d367c..ea9fed82d6 100644 --- a/src/main/java/org/mockito/quality/Strictness.java +++ b/src/main/java/org/mockito/quality/Strictness.java @@ -9,31 +9,31 @@ import org.mockito.exceptions.misusing.PotentialStubbingProblem; import org.mockito.exceptions.misusing.UnnecessaryStubbingException; import org.mockito.internal.junit.JUnitRule; +import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoRule; /** - * Configures the "strictness" of Mockito during a mocking session. - * A session typically maps to a single test method invocation. - * {@code Strictness} drives cleaner tests and better productivity. - * The easiest way to leverage enhanced {@code Strictness} is using - * Mockito's JUnit support ({@link MockitoRule} or {@link MockitoJUnitRunner}). - * If you cannot use JUnit support {@link MockitoSession} is the way to go. + * Configures the "strictness" of Mockito, affecting the behavior of stubbings and verification. + * "Strict stubbing" is a new feature in Mockito 2 that drives cleaner tests and better productivity. + * The easiest way to leverage it is via Mockito's JUnit support ({@link MockitoJUnit}) or Mockito Session ({@link MockitoSession}). *

    - * How strictness level influences the behavior of the test (mocking session)? + * How strictness influences the behavior of the test? *

      + *
    1. {@link Strictness#STRICT_STUBS} - ensures clean tests, reduces test code duplication, improves debuggability. + * Best combination of flexibility and productivity. Highly recommended. + * Planned as default for Mockito v3. + * Enable it via {@link MockitoRule}, {@link MockitoJUnitRunner} or {@link MockitoSession}. + * See {@link #STRICT_STUBS} for the details.
    2. *
    3. {@link Strictness#LENIENT} - no added behavior. * The default of Mockito 1.x. - * Recommended only if you cannot use {@link #STRICT_STUBS} nor {@link #WARN}.
    4. - *
    5. {@link Strictness#WARN} - helps keeping tests clean and improves debuggability. + * Recommended only if you cannot use {@link #STRICT_STUBS}
    6. + *
    7. {@link Strictness#WARN} - cleaner tests but only if you read the console output. * Reports console warnings about unused stubs * and stubbing argument mismatch (see {@link org.mockito.quality.MockitoHint}). - * The default behavior of Mockito 2.x when {@link JUnitRule} or {@link MockitoJUnitRunner} are used.
    8. + * The default behavior of Mockito 2.x when {@link JUnitRule} or {@link MockitoJUnitRunner} are used. * Recommended if you cannot use {@link #STRICT_STUBS}. - *
    9. {@link Strictness#STRICT_STUBS} - ensures clean tests, reduces test code duplication, improves debuggability. - * Best combination of flexibility and productivity. Highly recommended. - * Planned as default for Mockito v3. - * See {@link #STRICT_STUBS} for the details. + * Introduced originally with Mockito 2 because console warnings was the only compatible way of adding such feature.
    10. *
    * * @since 2.3.0 @@ -43,7 +43,7 @@ public enum Strictness { /** * No extra strictness. Mockito 1.x behavior. - * Recommended only if you cannot use {@link #STRICT_STUBS} nor {@link #WARN}. + * Recommended only if you cannot use {@link #STRICT_STUBS}. *

    * For more information see {@link Strictness}. * @@ -53,10 +53,10 @@ public enum Strictness { LENIENT, /** - * Helps keeping tests clean and improves debuggability. + * Helps keeping tests clean and improves debuggability only if you read the console output. * Extra warnings emitted to the console, see {@link MockitoHint}. * Default Mockito 2.x behavior. - * Recommended if you cannot use {@link #STRICT_STUBS}. + * Recommended only if you cannot use {@link #STRICT_STUBS} because console output is ignored most of the time. *

    * For more information see {@link Strictness}. * @@ -70,6 +70,7 @@ public enum Strictness { * Offers best combination of flexibility and productivity. * Highly recommended. * Planned as default for Mockito v3. + * Enable it via our JUnit support ({@link MockitoJUnit}) or {@link MockitoSession}. *

    * Adds following behavior: *

      @@ -80,8 +81,7 @@ public enum Strictness { *
    • Cleaner, more DRY tests ("Don't Repeat Yourself"): * If you use {@link org.mockito.Mockito#verifyNoMoreInteractions(Object...)} * you no longer need to explicitly verify stubbed invocations. - * They are automatically verified for you. However if you have more invocations, - * the test won't fail since it won't check that there are no more interactions on that stub.
    • + * They are automatically verified for you. *
    * * For more information see {@link Strictness}. diff --git a/src/main/java/org/mockito/stubbing/BaseStubber.java b/src/main/java/org/mockito/stubbing/BaseStubber.java new file mode 100644 index 0000000000..8c3192815e --- /dev/null +++ b/src/main/java/org/mockito/stubbing/BaseStubber.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.stubbing; + +import org.mockito.Mockito; +import org.mockito.NotExtensible; + +/** + * Base interface for stubbing consecutive method calls with {@link Mockito#doReturn(Object)} syntax. + * This interface is needed so that we can reuse the same hierarchy in subinterfaces. + * + * @since 2.20.0 + */ +@NotExtensible +public interface BaseStubber { + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Throwable[])} style: + *
    
    +     *   doThrow(new RuntimeException("one")).
    +     *   doThrow(new RuntimeException("two"))
    +     *       .when(mock).someVoidMethod();
    +     * 
    + * See javadoc for {@link Mockito#doThrow(Throwable[])} + * + * @param toBeThrown to be thrown when the stubbed method is called + * @return stubber - to select a method for stubbing + */ + Stubber doThrow(Throwable... toBeThrown); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Class)} style: + *
    
    +     *   doThrow(RuntimeException.class).
    +     *   doThrow(IllegalArgumentException.class)
    +     *       .when(mock).someVoidMethod();
    +     * 
    + * See javadoc for {@link Mockito#doThrow(Class)} + * + * @param toBeThrown exception class to be thrown when the stubbed method is called + * @return stubber - to select a method for stubbing + * + * @since 2.1.0 + */ + Stubber doThrow(Class toBeThrown); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Class)} style: + *
    
    +     *   doThrow(RuntimeException.class).
    +     *   doThrow(IllegalArgumentException.class)
    +     *       .when(mock).someVoidMethod();
    +     * 
    + * See javadoc for {@link Mockito#doThrow(Class)} + * + * @param toBeThrown exception class to be thrown when the stubbed method is called + * @param nextToBeThrown exception class next to be thrown when the stubbed method is called + * @return stubber - to select a method for stubbing + * + * @since 2.1.0 + */ + // Additional method helps users of JDK7+ to hide heap pollution / unchecked generics array creation + @SuppressWarnings ({"unchecked", "varargs"}) + Stubber doThrow(Class toBeThrown, Class... nextToBeThrown); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doAnswer(Answer)} style: + *
    
    +     *   doAnswer(answerOne).
    +     *   doAnswer(answerTwo)
    +     *       .when(mock).someVoidMethod();
    +     * 
    + * See javadoc for {@link Mockito#doAnswer(Answer)} + * + * @param answer to answer when the stubbed method is called + * @return stubber - to select a method for stubbing + */ + Stubber doAnswer(Answer answer); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doNothing()} style: + *
    
    +     *   doNothing().
    +     *   doThrow(new RuntimeException("two"))
    +     *       .when(mock).someVoidMethod();
    +     * 
    + * See javadoc for {@link Mockito#doNothing()} + * + * @return stubber - to select a method for stubbing + */ + Stubber doNothing(); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doReturn(Object)} style. + *

    + * See javadoc for {@link Mockito#doReturn(Object)} + * + * @param toBeReturned to be returned when the stubbed method is called + * @return stubber - to select a method for stubbing + */ + Stubber doReturn(Object toBeReturned); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doReturn(Object)} style. + *

    + * See javadoc for {@link Mockito#doReturn(Object, Object...)} + * + * @param toBeReturned to be returned when the stubbed method is called + * @param nextToBeReturned to be returned in consecutive calls when the stubbed method is called + * @return stubber - to select a method for stubbing + */ + @SuppressWarnings({"unchecked", "varargs"}) + Stubber doReturn(Object toBeReturned, Object... nextToBeReturned); + + /** + * Use it for stubbing consecutive calls in {@link Mockito#doCallRealMethod()} style. + *

    + * See javadoc for {@link Mockito#doCallRealMethod()} + * + * @return stubber - to select a method for stubbing + */ + Stubber doCallRealMethod(); +} diff --git a/src/main/java/org/mockito/stubbing/LenientStubber.java b/src/main/java/org/mockito/stubbing/LenientStubber.java new file mode 100644 index 0000000000..9088399aaa --- /dev/null +++ b/src/main/java/org/mockito/stubbing/LenientStubber.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.stubbing; + +import org.mockito.CheckReturnValue; +import org.mockito.Mockito; +import org.mockito.NotExtensible; + +/** + * Used for declaring optional stubbings with {@link Mockito#lenient()} + * + * @since 2.20.0 + */ +@NotExtensible +public interface LenientStubber extends BaseStubber { + + /** + * Allows declaring the method to stub. See {@link Mockito#when(Object)}. + * Needed for classic stubbing with when().then() + * + * @since 2.20.0 + */ + @CheckReturnValue + OngoingStubbing when(T methodCall); +} diff --git a/src/main/java/org/mockito/stubbing/Stubber.java b/src/main/java/org/mockito/stubbing/Stubber.java index b7507a71d6..5de954bf78 100644 --- a/src/main/java/org/mockito/stubbing/Stubber.java +++ b/src/main/java/org/mockito/stubbing/Stubber.java @@ -5,6 +5,7 @@ package org.mockito.stubbing; import org.mockito.Mockito; +import org.mockito.NotExtensible; /** * Allows to choose a method when stubbing in doThrow()|doAnswer()|doNothing()|doReturn() style @@ -39,7 +40,8 @@ * See examples in javadoc for {@link Mockito} */ @SuppressWarnings("unchecked") -public interface Stubber { +@NotExtensible +public interface Stubber extends BaseStubber { /** * Allows to choose a method when stubbing in doThrow()|doAnswer()|doNothing()|doReturn() style @@ -70,111 +72,4 @@ public interface Stubber { * @return select method for stubbing */ T when(T mock); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Throwable[])} style: - *

    
    -     *   doThrow(new RuntimeException("one")).
    -     *   doThrow(new RuntimeException("two"))
    -     *       .when(mock).someVoidMethod();
    -     * 
    - * See javadoc for {@link Mockito#doThrow(Throwable[])} - * - * @param toBeThrown to be thrown when the stubbed method is called - * @return stubber - to select a method for stubbing - */ - Stubber doThrow(Throwable... toBeThrown); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Class)} style: - *
    
    -     *   doThrow(RuntimeException.class).
    -     *   doThrow(IllegalArgumentException.class)
    -     *       .when(mock).someVoidMethod();
    -     * 
    - * See javadoc for {@link Mockito#doThrow(Class)} - * - * @param toBeThrown exception class to be thrown when the stubbed method is called - * @return stubber - to select a method for stubbing - * - * @since 2.1.0 - */ - Stubber doThrow(Class toBeThrown); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doThrow(Class)} style: - *
    
    -     *   doThrow(RuntimeException.class).
    -     *   doThrow(IllegalArgumentException.class)
    -     *       .when(mock).someVoidMethod();
    -     * 
    - * See javadoc for {@link Mockito#doThrow(Class)} - * - * @param toBeThrown exception class to be thrown when the stubbed method is called - * @param nextToBeThrown exception class next to be thrown when the stubbed method is called - * @return stubber - to select a method for stubbing - * - * @since 2.1.0 - */ - // Additional method helps users of JDK7+ to hide heap pollution / unchecked generics array creation - @SuppressWarnings ({"unchecked", "varargs"}) - Stubber doThrow(Class toBeThrown, Class... nextToBeThrown); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doAnswer(Answer)} style: - *
    
    -     *   doAnswer(answerOne).
    -     *   doAnswer(answerTwo)
    -     *       .when(mock).someVoidMethod();
    -     * 
    - * See javadoc for {@link Mockito#doAnswer(Answer)} - * - * @param answer to answer when the stubbed method is called - * @return stubber - to select a method for stubbing - */ - Stubber doAnswer(Answer answer); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doNothing()} style: - *
    
    -     *   doNothing().
    -     *   doThrow(new RuntimeException("two"))
    -     *       .when(mock).someVoidMethod();
    -     * 
    - * See javadoc for {@link Mockito#doNothing()} - * - * @return stubber - to select a method for stubbing - */ - Stubber doNothing(); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doReturn(Object)} style. - *

    - * See javadoc for {@link Mockito#doReturn(Object)} - * - * @param toBeReturned to be returned when the stubbed method is called - * @return stubber - to select a method for stubbing - */ - Stubber doReturn(Object toBeReturned); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doReturn(Object)} style. - *

    - * See javadoc for {@link Mockito#doReturn(Object, Object...)} - * - * @param toBeReturned to be returned when the stubbed method is called - * @param nextToBeReturned to be returned in consecutive calls when the stubbed method is called - * @return stubber - to select a method for stubbing - */ - @SuppressWarnings({"unchecked", "varargs"}) - Stubber doReturn(Object toBeReturned, Object... nextToBeReturned); - - /** - * Use it for stubbing consecutive calls in {@link Mockito#doCallRealMethod()} style. - *

    - * See javadoc for {@link Mockito#doCallRealMethod()} - * - * @return stubber - to select a method for stubbing - */ - Stubber doCallRealMethod(); } diff --git a/src/main/java/org/mockito/stubbing/Stubbing.java b/src/main/java/org/mockito/stubbing/Stubbing.java index 09856fbbe2..cba0917f97 100644 --- a/src/main/java/org/mockito/stubbing/Stubbing.java +++ b/src/main/java/org/mockito/stubbing/Stubbing.java @@ -4,9 +4,12 @@ */ package org.mockito.stubbing; +import org.mockito.Incubating; import org.mockito.MockingDetails; +import org.mockito.Mockito; import org.mockito.NotExtensible; import org.mockito.invocation.Invocation; +import org.mockito.quality.Strictness; /** * Stubbing declared on the mock object. @@ -50,4 +53,13 @@ public interface Stubbing extends Answer { * @since 2.2.3 */ boolean wasUsed(); + + /** + * Informs about the {@link Strictness} level of this stubbing. + * For more information about setting strictness for stubbings see {@link Mockito#lenient()}. + * + * @since 2.20.0 + */ + @Incubating + Strictness getStrictness(); } diff --git a/src/test/java/org/mockito/internal/junit/UnusedStubbingsTest.java b/src/test/java/org/mockito/internal/junit/UnusedStubbingsTest.java index 8d11dc2b75..af1d3a186b 100644 --- a/src/test/java/org/mockito/internal/junit/UnusedStubbingsTest.java +++ b/src/test/java/org/mockito/internal/junit/UnusedStubbingsTest.java @@ -4,9 +4,6 @@ */ package org.mockito.internal.junit; -import java.util.Arrays; -import java.util.Collections; - import org.junit.Test; import org.mockito.internal.invocation.InvocationBuilder; import org.mockito.internal.stubbing.StubbedInvocationMatcher; @@ -14,6 +11,9 @@ import org.mockito.stubbing.Stubbing; import org.mockitoutil.TestBase; +import java.util.Arrays; +import java.util.Collections; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.internal.stubbing.answers.DoesNothing.doesNothing; @@ -38,8 +38,8 @@ public void no_unused_stubbings() throws Exception { public void unused_stubbings() throws Exception { //given UnusedStubbings stubbings = new UnusedStubbings(Arrays.asList( - new StubbedInvocationMatcher(new InvocationBuilder().toInvocationMatcher(), doesNothing()), - new StubbedInvocationMatcher(new InvocationBuilder().toInvocationMatcher(), doesNothing()) + new StubbedInvocationMatcher(doesNothing(), new InvocationBuilder().toInvocationMatcher(), null), + new StubbedInvocationMatcher(doesNothing(), new InvocationBuilder().toInvocationMatcher(), null) )); diff --git a/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplStubbingTest.java b/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplStubbingTest.java index de5eb13454..52e481d1c8 100644 --- a/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplStubbingTest.java +++ b/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplStubbingTest.java @@ -46,7 +46,7 @@ public void setup() { public void should_finish_stubbing_when_wrong_throwable_is_set() throws Exception { state.stubbingStarted(); try { - invocationContainerImpl.addAnswer(new ThrowsException(new Exception())); + invocationContainerImpl.addAnswer(new ThrowsException(new Exception()), null); fail(); } catch (MockitoException e) { state.validateState(); @@ -56,18 +56,18 @@ public void should_finish_stubbing_when_wrong_throwable_is_set() throws Exceptio @Test public void should_finish_stubbing_on_adding_return_value() throws Exception { state.stubbingStarted(); - invocationContainerImpl.addAnswer(new Returns("test")); + invocationContainerImpl.addAnswer(new Returns("test"), null); state.validateState(); } @Test public void should_get_results_for_methods() throws Throwable { invocationContainerImpl.setInvocationForPotentialStubbing(new InvocationMatcher(simpleMethod)); - invocationContainerImpl.addAnswer(new Returns("simpleMethod")); + invocationContainerImpl.addAnswer(new Returns("simpleMethod"), null); Invocation differentMethod = new InvocationBuilder().differentMethod().toInvocation(); invocationContainerImpl.setInvocationForPotentialStubbing(new InvocationMatcher(differentMethod)); - invocationContainerImpl.addAnswer(new ThrowsException(new MyException())); + invocationContainerImpl.addAnswer(new ThrowsException(new MyException()), null); assertEquals("simpleMethod", invocationContainerImpl.answerTo(simpleMethod)); @@ -80,11 +80,11 @@ public void should_get_results_for_methods() throws Throwable { @Test public void should_get_results_for_methods_stub_only() throws Throwable { invocationContainerImplStubOnly.setInvocationForPotentialStubbing(new InvocationMatcher(simpleMethod)); - invocationContainerImplStubOnly.addAnswer(new Returns("simpleMethod")); + invocationContainerImplStubOnly.addAnswer(new Returns("simpleMethod"), null); Invocation differentMethod = new InvocationBuilder().differentMethod().toInvocation(); invocationContainerImplStubOnly.setInvocationForPotentialStubbing(new InvocationMatcher(differentMethod)); - invocationContainerImplStubOnly.addAnswer(new ThrowsException(new MyException())); + invocationContainerImplStubOnly.addAnswer(new ThrowsException(new MyException()), null); assertEquals("simpleMethod", invocationContainerImplStubOnly.answerTo(simpleMethod)); @@ -97,7 +97,7 @@ public void should_get_results_for_methods_stub_only() throws Throwable { @Test public void should_validate_throwable() throws Throwable { try { - invocationContainerImpl.addAnswer(new ThrowsException(null)); + invocationContainerImpl.addAnswer(new ThrowsException(null), null); fail(); } catch (MockitoException e) {} } diff --git a/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplTest.java b/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplTest.java index 176372f79d..c63d6e96ae 100644 --- a/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplTest.java +++ b/src/test/java/org/mockito/internal/stubbing/InvocationContainerImplTest.java @@ -4,12 +4,6 @@ */ package org.mockito.internal.stubbing; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.LinkedList; -import java.util.concurrent.CountDownLatch; import org.junit.Test; import org.mockito.internal.creation.MockSettingsImpl; import org.mockito.internal.invocation.InvocationBuilder; @@ -19,6 +13,13 @@ import org.mockito.invocation.Invocation; import org.mockito.mock.MockCreationSettings; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + /** * Author: Szczepan Faber */ @@ -54,7 +55,7 @@ public void run() { throw new RuntimeException(e); } c.setInvocationForPotentialStubbing(new InvocationMatcher(invocation)); - c.addAnswer(new Returns("foo")); + c.addAnswer(new Returns("foo"), null); c.findAnswerFor(invocation); } }; @@ -98,7 +99,7 @@ public void should_tell_if_has_invocation_for_potential_stubbing() throws Except container.setInvocationForPotentialStubbing(new InvocationBuilder().toInvocationMatcher()); assertTrue(container.hasInvocationForPotentialStubbing()); - container.addAnswer(new ReturnsEmptyValues()); + container.addAnswer(new ReturnsEmptyValues(), null); assertFalse(container.hasInvocationForPotentialStubbing()); } @@ -107,7 +108,7 @@ public void should_tell_if_has_invocation_for_potential_stubbing_stub_only() thr containerStubOnly.setInvocationForPotentialStubbing(new InvocationBuilder().toInvocationMatcher()); assertTrue(containerStubOnly.hasInvocationForPotentialStubbing()); - containerStubOnly.addAnswer(new ReturnsEmptyValues()); + containerStubOnly.addAnswer(new ReturnsEmptyValues(), null); assertFalse(containerStubOnly.hasInvocationForPotentialStubbing()); } } diff --git a/src/test/java/org/mockitousage/internal/junit/UnusedStubbingsFinderTest.java b/src/test/java/org/mockitousage/internal/junit/UnusedStubbingsFinderTest.java index b03d5d7959..0eb3d7d346 100644 --- a/src/test/java/org/mockitousage/internal/junit/UnusedStubbingsFinderTest.java +++ b/src/test/java/org/mockitousage/internal/junit/UnusedStubbingsFinderTest.java @@ -16,6 +16,7 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; /** @@ -85,11 +86,29 @@ public void some_unused_stubbings() throws Exception { stubbings.toString()); } + @Test + public void unused_and_lenient_stubbings() throws Exception { + when(mock1.simpleMethod(1)).thenReturn("1"); + when(mock1.simpleMethod(2)).thenReturn("2"); + lenient().when(mock2.simpleMethod(3)).thenReturn("3"); + + mock1.simpleMethod(1); + + //when + UnusedStubbings stubbings = finder.getUnusedStubbings((List) asList(mock1, mock2)); + + //then + assertEquals(1, stubbings.size()); + assertEquals("[mock1.simpleMethod(2); stubbed with: [Returns: 2]]", + stubbings.toString()); + } + @Test public void some_unused_stubbings_by_location() throws Exception { when(mock1.simpleMethod(1)).thenReturn("1"); when(mock2.simpleMethod(2)).thenReturn("2"); when(mock2.simpleMethod(3)).thenReturn("3"); + lenient().when(mock2.differentMethod()).thenReturn("4"); //will not be included in results mock2.simpleMethod(2); diff --git a/src/test/java/org/mockitousage/strictness/StrictnessPerMockTest.java b/src/test/java/org/mockitousage/strictness/StrictnessPerMockTest.java new file mode 100644 index 0000000000..2dce3e05e4 --- /dev/null +++ b/src/test/java/org/mockitousage/strictness/StrictnessPerMockTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage.strictness; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.ThrowableAssert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; +import org.mockito.exceptions.verification.NoInteractionsWanted; +import org.mockito.quality.Strictness; +import org.mockitousage.IMethods; +import org.mockitoutil.TestBase; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.withSettings; + +//TODO 792 also move other Strictness tests to this package (unless they already have good package) +public class StrictnessPerMockTest { + + MockitoSession mockito; + @Mock IMethods strictStubsMock; + IMethods lenientMock; + + @Before + public void before() { + mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking(); + assertNull(lenientMock); + lenientMock = mock(IMethods.class, withSettings().lenient()); + } + + @Test + public void knows_if_mock_is_lenient() { + assertTrue(mockingDetails(lenientMock).getMockCreationSettings().isLenient()); + assertFalse(mockingDetails(strictStubsMock).getMockCreationSettings().isLenient()); + } + + @Test + public void potential_stubbing_problem() { + //when + given(lenientMock.simpleMethod(100)).willReturn("100"); + given(strictStubsMock.simpleMethod(100)).willReturn("100"); + + //then on lenient mock (created by hand), we can call the stubbed method with different arg: + lenientMock.simpleMethod(200); + + //and on strict stub mock (created by session), we cannot call stubbed method with different arg: + Assertions.assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + strictStubsMock.simpleMethod(200); + } + }).isInstanceOf(PotentialStubbingProblem.class); + } + + @Test + public void unnecessary_stubbing() { + //when + given(lenientMock.simpleMethod(100)).willReturn("100"); + given(strictStubsMock.simpleMethod(100)).willReturn("100"); + + //then unnecessary stubbing flags method only on the strict stub mock: + Assertions.assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mockito.finishMocking(); + } + }).isInstanceOf(UnnecessaryStubbingException.class) + .hasMessageContaining("1. -> ") + //good enough to prove that we're flagging just one unnecessary stubbing: + //TODO 792: let's make UnnecessaryStubbingException exception contain the Invocation instance + //so that we can write clean assertion rather than depending on string + .isNot(TestBase.hasMessageContaining("2. ->")); + } + + @Test + public void verify_no_more_invocations() { + //when + given(lenientMock.simpleMethod(100)).willReturn("100"); + given(strictStubsMock.simpleMethod(100)).willReturn("100"); + + //and: + strictStubsMock.simpleMethod(100); + lenientMock.simpleMethod(100); + + //then 'verifyNoMoreInteractions' ignores strict stub (implicitly verified) but flags the lenient mock + Assertions.assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + verifyNoMoreInteractions(strictStubsMock, lenientMock); + } + }).isInstanceOf(NoInteractionsWanted.class) + .hasMessageContaining("But found this interaction on mock 'iMethods'") + //TODO 792: let's make NoInteractionsWanted exception contain the Invocation instances + //so that we can write clean assertion rather than depending on string + .hasMessageContaining("Actually, above is the only interaction with this mock"); + } + + @After + public void after() { + mockito.finishMocking(); + } +} diff --git a/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingTest.java b/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingTest.java new file mode 100644 index 0000000000..3838beef7a --- /dev/null +++ b/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage.strictness; + +import org.assertj.core.api.ThrowableAssert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.AdditionalAnswers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; +import org.mockito.exceptions.verification.NoInteractionsWanted; +import org.mockito.quality.Strictness; +import org.mockitousage.IMethods; +import org.mockitoutil.TestBase; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class StrictnessPerStubbingTest { + + MockitoSession mockito; + @Mock IMethods mock; + + @Before + public void before() { + mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking(); + } + + @Test + public void potential_stubbing_problem() { + //when + when(mock.simpleMethod("1")).thenReturn("1"); + lenient().when(mock.differentMethod("2")).thenReturn("2"); + + //then on lenient stubbing, we can call it with different argument: + mock.differentMethod("200"); + + //but on strict stubbing, we cannot: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mock.simpleMethod("100"); + } + }).isInstanceOf(PotentialStubbingProblem.class); + } + + @Test + public void doReturn_syntax() { + //when + lenient().doReturn("2").doReturn("3") + .when(mock).simpleMethod(1); + + //then on lenient stubbing, we can call it with different argument: + mock.simpleMethod(200); + + //and stubbing works, too: + assertEquals("2", mock.simpleMethod(1)); + assertEquals("3", mock.simpleMethod(1)); + } + + @Test + public void doReturn_varargs_syntax() { + //when + lenient().doReturn("2", "3") + .when(mock).simpleMethod(1); + + //then on lenient stubbing, we can call it with different argument with no exception: + mock.simpleMethod(200); + + //and stubbing works, too: + assertEquals("2", mock.simpleMethod(1)); + assertEquals("3", mock.simpleMethod(1)); + } + + @Test + public void doThrow_syntax() { + //when + lenient() + .doThrow(IllegalArgumentException.class) + .doThrow(IllegalStateException.class) + .when(mock).simpleMethod(1); + + //then on lenient stubbing, we can call it with different argument with no exception: + mock.simpleMethod(200); + + //and stubbing works, too: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalArgumentException.class); + + //testing consecutive call: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalStateException.class); + } + + @Test + public void doThrow_vararg_syntax() { + //when + lenient() + .doThrow(IllegalArgumentException.class, IllegalStateException.class) + .when(mock).simpleMethod(1); + + //then on lenient stubbing, we can call it with different argument with no exception: + mock.simpleMethod(200); + + //and stubbing works, too: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalArgumentException.class); + + //testing consecutive call: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalStateException.class); + } + + @Test + public void doThrow_instance_vararg_syntax() { + //when + lenient() + .doThrow(new IllegalArgumentException(), new IllegalStateException()) + .when(mock).simpleMethod(1); + + //then on lenient stubbing, we can call it with different argument with no exception: + mock.simpleMethod(200); + + //and stubbing works, too: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalArgumentException.class); + + //testing consecutive call: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() throws Throwable { + mock.simpleMethod(1); + } + }).isInstanceOf(IllegalStateException.class); + } + + static class Counter { + int increment(int x) { + return x + 1; + } + void scream(String message) { + throw new RuntimeException(message); + } + } + + @Test + public void doCallRealMethod_syntax() { + //when + Counter mock = mock(Counter.class); + lenient().doCallRealMethod().when(mock).increment(1); + + //then no exception and default return value if we call it with different arg: + assertEquals(0, mock.increment(0)); + + //and real method is called when using correct arg: + assertEquals(2, mock.increment(1)); + } + + @Test + public void doNothing_syntax() { + //when + final Counter spy = spy(Counter.class); + lenient().doNothing().when(spy).scream("1"); + + //then no stubbing exception and real method is called if we call stubbed method with different arg: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + spy.scream("2"); + } + }).hasMessage("2"); + + //and we do nothing when stubbing called with correct arg: + spy.scream("1"); + } + + @Test + public void doAnswer_syntax() { + //when + lenient().doAnswer(AdditionalAnswers.returnsFirstArg()).when(mock).simpleMethod("1"); + + //then on lenient stubbing, we can call it with different argument: + mock.simpleMethod("200"); + + //and stubbing works, too: + assertEquals("1", mock.simpleMethod("1")); + } + + @Test + public void unnecessary_stubbing() { + //when + when(mock.simpleMethod("1")).thenReturn("1"); + lenient().when(mock.differentMethod("2")).thenReturn("2"); + + //then unnecessary stubbing flags method only on the strict stubbing: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mockito.finishMocking(); + } + }).isInstanceOf(UnnecessaryStubbingException.class) + .hasMessageContaining("1. -> ") + //good enough to prove that we're flagging just one unnecessary stubbing: + //TODO 792: this assertion is duplicated with StrictnessPerMockTest + .isNot(TestBase.hasMessageContaining("2. ->")); + } + + @Test + public void unnecessary_stubbing_with_doReturn() { + //when + lenient().doReturn("2").when(mock).differentMethod("2"); + + //then no exception is thrown: + mockito.finishMocking(); + } + + @Test + public void verify_no_more_invocations() { + //when + when(mock.simpleMethod("1")).thenReturn("1"); + lenient().when(mock.differentMethod("2")).thenReturn("2"); + + //and: + mock.simpleMethod("1"); + mock.differentMethod("200"); // <- different arg + + //then 'verifyNoMoreInteractions' flags the lenient stubbing (called with different arg) + //and reports it with [?] in the exception message + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + verifyNoMoreInteractions(mock); + } + }).isInstanceOf(NoInteractionsWanted.class) + .hasMessageContaining("1. ->") + .hasMessageContaining("2. [?]->"); + //TODO 792: assertion duplicated with StrictnessPerMockTest + // and we should use assertions based on content of the exception rather than the string + } + + @After + public void after() { + mockito.finishMocking(); + } +} diff --git a/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingWithRunnerTest.java b/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingWithRunnerTest.java new file mode 100644 index 0000000000..e42d340003 --- /dev/null +++ b/src/test/java/org/mockitousage/strictness/StrictnessPerStubbingWithRunnerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage.strictness; + +import org.assertj.core.api.ThrowableAssert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockitousage.IMethods; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class StrictnessPerStubbingWithRunnerTest { + + @Mock IMethods mock; + + @Test + public void potential_stubbing_problem() { + //when + when(mock.simpleMethod("1")).thenReturn("1"); + lenient().when(mock.differentMethod("2")).thenReturn("2"); + + //then on lenient stubbing, we can call it with different argument: + mock.differentMethod("200"); + + //but on strict stubbing, we cannot: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mock.simpleMethod("100"); + } + }).isInstanceOf(PotentialStubbingProblem.class); + + //let's use the strict stubbing so that it is not reported as failure by the runner: + mock.simpleMethod("1"); + } + + @Test + public void unnecessary_stubbing() { + //this unnecessary stubbing is not flagged by the runner: + lenient().when(mock.differentMethod("2")).thenReturn("2"); + } +} diff --git a/src/test/java/org/mockitousage/strictness/StrictnessWhenRuleStrictnessIsUpdatedTest.java b/src/test/java/org/mockitousage/strictness/StrictnessWhenRuleStrictnessIsUpdatedTest.java new file mode 100644 index 0000000000..592cca9949 --- /dev/null +++ b/src/test/java/org/mockitousage/strictness/StrictnessWhenRuleStrictnessIsUpdatedTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage.strictness; + +import org.assertj.core.api.ThrowableAssert; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.quality.Strictness; +import org.mockitousage.IMethods; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +public class StrictnessWhenRuleStrictnessIsUpdatedTest { + + @Mock IMethods mock; + @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.LENIENT); + + @Test + public void strictness_per_mock() { + //when + rule.strictness(Strictness.STRICT_STUBS); + + //then previous mock is strict: + when(mock.simpleMethod(1)).thenReturn("1"); + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mock.simpleMethod(2); + } + }).isInstanceOf(PotentialStubbingProblem.class); + + //but the new mock is lenient, even though the rule is not: + final IMethods lenientMock = mock(IMethods.class, withSettings().lenient()); + when(lenientMock.simpleMethod(1)).thenReturn("1"); + lenientMock.simpleMethod(100); + } + + @Test + public void strictness_per_stubbing() { + //when + rule.strictness(Strictness.STRICT_STUBS); + + //then previous mock is strict: + when(mock.simpleMethod(1)).thenReturn("1"); + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mock.simpleMethod(2); + } + }).isInstanceOf(PotentialStubbingProblem.class); + + //but the new mock is lenient, even though the rule is not: + lenient().when(mock.simpleMethod(1)).thenReturn("1"); + mock.simpleMethod(100); + } +} diff --git a/src/test/java/org/mockitousage/strictness/StrictnessWithRulesTest.java b/src/test/java/org/mockitousage/strictness/StrictnessWithRulesTest.java new file mode 100644 index 0000000000..3a323fb2f4 --- /dev/null +++ b/src/test/java/org/mockitousage/strictness/StrictnessWithRulesTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockitousage.strictness; + +import org.assertj.core.api.ThrowableAssert; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.quality.Strictness; +import org.mockitousage.IMethods; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +public class StrictnessWithRulesTest { + + @Mock IMethods mock; + @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + + @Test + public void potential_stubbing_problem() { + //when + when(mock.simpleMethod("1")).thenReturn("1"); + lenient().when(mock.differentMethod("2")).thenReturn("2"); + + //then on lenient stubbing, we can call it with different argument: + mock.differentMethod("200"); + + //but on strict stubbing, we cannot: + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() throws Throwable { + mock.simpleMethod("100"); + } + }).isInstanceOf(PotentialStubbingProblem.class); + + //let's use the strict stubbing so that it is not reported as failure by the rule: + mock.simpleMethod("1"); + } + + @Test + public void unnecessary_stubbing() { + //this unnecessary stubbing is not flagged by the rule: + lenient().when(mock.differentMethod("2")).thenReturn("2"); + } +} diff --git a/src/test/java/org/mockitoutil/TestBase.java b/src/test/java/org/mockitoutil/TestBase.java index ca3d7b8744..98066ebc33 100644 --- a/src/test/java/org/mockitoutil/TestBase.java +++ b/src/test/java/org/mockitoutil/TestBase.java @@ -5,6 +5,7 @@ package org.mockitoutil; +import org.assertj.core.api.Condition; import org.junit.After; import org.junit.Before; import org.mockito.MockitoAnnotations; @@ -31,6 +32,18 @@ */ public class TestBase { + /** + * Condition to be used with AssertJ + */ + public static Condition hasMessageContaining(final String substring) { + return new Condition() { + @Override + public boolean matches(Throwable e) { + return e.getMessage().contains(substring); + } + }; + } + @After public void cleanUpConfigInAnyCase() { ConfigurationAccess.getConfig().overrideCleansStackTrace(false); diff --git a/version.properties b/version.properties index dbf9e8dfb8..6a56cf9978 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ #Currently building Mockito version -version=2.19.7 +version=2.20.0 #Previous version used to generate release notes delta previousVersion=2.19.6