Skip to content

Commit

Permalink
Merge pull request #1272 from mockito/fine-strictness
Browse files Browse the repository at this point in the history
Strictness configurable per mock / stubbing
[ci maven-central-release]
  • Loading branch information
mockitoguy committed Jul 24, 2018
2 parents 940e9ac + 0fcc2a3 commit d2145dd
Show file tree
Hide file tree
Showing 44 changed files with 1,281 additions and 275 deletions.
2 changes: 1 addition & 1 deletion src/main/java/org/mockito/CheckReturnValue.java
Expand Up @@ -28,5 +28,5 @@
ElementType.TYPE
})
@Retention(RetentionPolicy.CLASS)
@interface CheckReturnValue {
public @interface CheckReturnValue {
}
17 changes: 17 additions & 0 deletions src/main/java/org/mockito/MockSettings.java
Expand Up @@ -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;
Expand Down Expand Up @@ -316,4 +319,18 @@ public interface MockSettings extends Serializable {
*/
@Incubating
<T> MockCreationSettings<T> build(Class<T> 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}.
*
* <pre class="code"><code class="java">
* Foo mock = mock(Foo.class, withSettings.lenient());
* </code></pre>
*
* For more information and an elaborate example, see {@link Mockito#lenient()}.
*/
@Incubating
MockSettings lenient();
}
114 changes: 104 additions & 10 deletions src/main/java/org/mockito/Mockito.java
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -94,12 +97,13 @@
* <a href="#37">37. Java 8 Custom Answer Support (Since 2.1.0)</a><br/>
* <a href="#38">38. Meta data and generic type retention (Since 2.1.0)</a><br/>
* <a href="#39">39. Mocking final types, enums and final methods (Since 2.1.0)</a><br/>
* <a href="#40">40. (*new*) Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+)</a><br/>
* <a href="#41">41. (**new**) Advanced public API for framework integrations (Since 2.10.+)</a><br/>
* <a href="#42">42. (**new**) New API for integrations: listening on verification start events (Since 2.11.+)</a><br/>
* <a href="#43">43. (**new**) New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a><br/>
* <a href="#40">40. Improved productivity and cleaner tests with "stricter" Mockito (Since 2.+)</a><br/>
* <a href="#41">41. Advanced public API for framework integrations (Since 2.10.+)</a><br/>
* <a href="#42">42. New API for integrations: listening on verification start events (Since 2.11.+)</a><br/>
* <a href="#43">43. New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a><br/>
* <a href="#44">44. Deprecated <code>org.mockito.plugins.InstantiatorProvider</code> as it was leaking internal API. it was replaced by <code>org.mockito.plugins.InstantiatorProvider2 (Since 2.15.4)</code></a><br/>
* <a href="#45">45. (**new**) New JUnit Jupiter (JUnit5+) extension</a><br/>
* <a href="#45">45. New JUnit Jupiter (JUnit5+) extension</a><br/>
* <a href="#46">46. New <code>Mockito.lenient()</code> and <code>MockSettings.lenient()</code> methods (Since 2.20.0</a><br/>
* </b>
*
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
Expand Down Expand Up @@ -1366,7 +1370,7 @@
* <code>org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker</code>
*
* <h3 id="40">40. <a class="meaningful_link" href="#strict_mockito" name="strict_mockito">
* (*new*) Improved productivity and cleaner tests with "stricter" Mockito</a> (Since 2.+)</h3>
* Improved productivity and cleaner tests with "stricter" Mockito</a> (Since 2.+)</h3>
*
* To quickly find out how "stricter" Mockito can make you more productive and get your tests cleaner, see:
* <ul>
Expand Down Expand Up @@ -1394,7 +1398,7 @@
* <a href="https://github.com/mockito/mockito/issues/769">issue 769</a>.
*
* <h3 id="41">41. <a class="meaningful_link" href="#framework_integrations_api" name="framework_integrations_api">
* (**new**) Advanced public API for framework integrations (Since 2.10.+)</a></h3>
* Advanced public API for framework integrations (Since 2.10.+)</a></h3>
*
* In Summer 2017 we decided that Mockito
* <a href="https://www.linkedin.com/pulse/mockito-vs-powermock-opinionated-dogmatic-static-mocking-faber">
Expand Down Expand Up @@ -1447,7 +1451,7 @@
* Do you have feedback? Please leave comment in <a href="https://github.com/mockito/mockito/issues/1110">issue 1110</a>.
*
* <h3 id="42">42. <a class="meaningful_link" href="#verifiation_started_listener" name="verifiation_started_listener">
* (**new**) New API for integrations: listening on verification start events (Since 2.11.+)</a></h3>
* New API for integrations: listening on verification start events (Since 2.11.+)</a></h3>
*
* Framework integrations such as <a href="https://projects.spring.io/spring-boot">Spring Boot</a> needs public API to tackle double-proxy use case
* (<a href="https://github.com/mockito/mockito/issues/1191">issue 1191</a>).
Expand All @@ -1467,7 +1471,7 @@
* </ul>
*
* <h3 id="43">43. <a class="meaningful_link" href="#mockito_session_testing_frameworks" name="mockito_session_testing_frameworks">
* (**new**) New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a></h3>
* New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a></h3>
*
* <p>{@link MockitoSessionBuilder} and {@link MockitoSession} were enhanced to enable reuse by testing framework
* integrations (e.g. {@link MockitoRule} for JUnit):</p>
Expand Down Expand Up @@ -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.</p>
*
* <h3 id="45">45. (**new**) <a class="meaningful_link" href="#junit5_mockito" name="junit5_mockito">New JUnit Jupiter (JUnit5+) extension</a></h3>
* <h3 id="45">45. <a class="meaningful_link" href="#junit5_mockito" name="junit5_mockito">New JUnit Jupiter (JUnit5+) extension</a></h3>
*
* For integration with JUnit Jupiter (JUnit5+), use the `org.mockito:mockito-junit-jupiter` artifact.
* For more information about the usage of the integration, see <a href="http://javadoc.io/page/org.mockito/mockito-junit-jupiter/latest/org/mockito/junit/jupiter/MockitoExtension.html">the JavaDoc of <code>MockitoExtension</code></a>.
*
* <h3 id="46">46. <a class="meaningful_link" href="#mockito_lenient" name="mockito_lenient">
* New <code>Mockito.lenient()</code> and <code>MockSettings.lenient()</code> methods (Since 2.20.0)</a></h3>
*
* 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:
*
* <pre class="code"><code class="java">
* lenient().when(mock.foo()).thenReturn("ok");
* </code></pre>
*
* If you want all the stubbings on a given mock to be lenient, you can configure the mock accordingly:
*
* <pre class="code"><code class="java">
* Foo mock = Mockito.mock(Foo.class, withSettings().lenient());
* </code></pre>
*
* 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 {
Expand Down Expand Up @@ -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}.
*
* <pre class="code"><code class="java">
* lenient().when(mock.foo()).thenReturn("ok");
* </code></pre>
*
* Most mocks in most tests don't need leniency and should happily prosper with {@link Strictness#STRICT_STUBS}.
* <ul>
* <li>If a specific stubbing needs to be lenient - use this method</li>
* <li>If a specific mock need to have stubbings lenient - use {@link MockSettings#lenient()}</li>
* <li>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})</li>
*
* <h3>Elaborate example</h3>
*
* 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.
* <p>
* 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.
*
* <pre class="code"><code class="java">
* public class SomeTest {
*
* &#064;Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(STRICT_STUBS);
*
* &#064;Mock Foo foo;
* &#064;Mock Bar bar;
*
* &#064;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());
* }
*
* &#064;Test public void test1() {
* foo.foo();
* }
*
* &#064;Test public void test2() {
* foo.foo();
* }
*
* &#064;Test public void test3() {
* bar.bar();
* }
* }
* </code></pre>
*
* @since 2.20.0
*/
@Incubating
public static LenientStubber lenient() {
return MOCKITO_CORE.lenient();
}
}
Expand Up @@ -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
Expand Down Expand Up @@ -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.
* </li>
* <li>Reduce the strictness level in the test method (only for JUnit Rules):
* <pre class="code"><code class="java">
* public class ExampleTest {
* &#064;Rule
* public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
*
* &#064;Test public void exampleTest() {
* //Change the strictness level only for this test method:
* mockito.strictness(Strictness.LENIENT);
*
* //remaining test code
* }
* }
* </code></pre>
* Currently, reducing strictness is only available to JUnit rules.
* If you need it in a different context let us know at <a href="https://github.com/mockito/mockito/issues/857">issue 857</a>.
* </li>
* <li>Reduce the strictness level per stubbing, per mock or per test - see {@link Mockito#lenient()}</li>
* <li>To opt-out in Mockito 2.x, simply remove the strict stubbing setting in the test class.</li>
* </ol>
* <p>
Expand Down
Expand Up @@ -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:
* <ul>
* <li>JUnit rule - {@link MockitoRule#strictness(Strictness)} or {@link MockitoRule#silent()}</li>
* <li>JUnit runner - {@link MockitoJUnitRunner.Silent}</li>
* <li>Mockito session - {@link MockitoSession}</li>
* </ul>
* 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):
* <ol>
* <li>Declaring specific stubbing as 'lenient' - {@link Mockito#lenient()}</li>
* <li>Declaring specific mock as 'lenient' - {@link org.mockito.MockSettings#lenient()}</li>
* <li>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})</li>
* </ol>
*
* <p>
* Unnecessary stubbings are stubbed method calls that were never realized during test execution. Example:
* <pre class="code"><code class="java">
Expand All @@ -45,11 +44,6 @@
* Mockito JUnit Runner triggers <code>UnnecessaryStubbingException</code> 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.
* <p>
* 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) {
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/org/mockito/internal/MockitoCore.java
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -202,4 +209,8 @@ public Object[] ignoreStubs(Object... mocks) {
public MockingDetails mockingDetails(Object toInspect) {
return new DefaultMockingDetails(toInspect);
}

public LenientStubber lenient() {
return new DefaultLenientStubber();
}
}
Expand Up @@ -226,6 +226,12 @@ public <T> MockCreationSettings<T> build(Class<T> typeToMock) {
return validatedSettings(typeToMock, (CreationSettings<T>) this);
}

@Override
public MockSettings lenient() {
this.lenient = true;
return this;
}

private static <T> CreationSettings<T> validatedSettings(Class<T> typeToMock, CreationSettings<T> source) {
MockCreationValidator validator = new MockCreationValidator();

Expand Down
Expand Up @@ -37,6 +37,7 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
private boolean useConstructor;
private Object outerClassInstance;
private Object[] constructorArgs;
protected boolean lenient;

public CreationSettings() {}

Expand All @@ -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
Expand Down Expand Up @@ -153,4 +155,9 @@ public Object getOuterClassInstance() {
public boolean isStubOnly() {
return stubOnly;
}

@Override
public boolean isLenient() {
return lenient;
}
}

0 comments on commit d2145dd

Please sign in to comment.