Skip to content

Commit 70cf2d2

Browse files
Add support for customizing strictness via @Mock annotation and MockSettings (#2650)
Fixes #2648
1 parent 6ce278b commit 70cf2d2

13 files changed

+194
-7
lines changed

src/main/java/org/mockito/Mock.java

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.lang.annotation.Target;
1414

1515
import org.mockito.junit.MockitoJUnitRunner;
16+
import org.mockito.quality.Strictness;
1617
import org.mockito.stubbing.Answer;
1718

1819
/**
@@ -105,10 +106,21 @@
105106
boolean serializable() default false;
106107

107108
/**
109+
* @deprecated Use {@link Mock#strictness()} instead.
110+
*
108111
* Mock will be lenient, see {@link MockSettings#lenient()}.
109112
* For examples how to use 'Mock' annotation and parameters see {@link Mock}.
110113
*
111114
* @since 2.23.3
112115
*/
116+
@Deprecated
113117
boolean lenient() default false;
118+
119+
/**
120+
* Mock will have custom strictness, see {@link MockSettings#strictness(Strictness)}.
121+
* For examples how to use 'Mock' annotation and parameters see {@link Mock}.
122+
*
123+
* @since 4.6.0
124+
*/
125+
Strictness strictness() default Strictness.STRICT_STUBS;
114126
}

src/main/java/org/mockito/MockSettings.java

+20
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ public interface MockSettings extends Serializable {
350350
<T> MockCreationSettings<T> buildStatic(Class<T> classToMock);
351351

352352
/**
353+
* @deprecated Use {@link MockSettings#strictness(Strictness)} instead.
354+
*
353355
* Lenient mocks bypass "strict stubbing" validation (see {@link Strictness#STRICT_STUBS}).
354356
* When mock is declared as lenient none of its stubbings will be checked for potential stubbing problems such as
355357
* 'unnecessary stubbing' ({@link UnnecessaryStubbingException}) or for 'stubbing argument mismatch' {@link PotentialStubbingProblem}.
@@ -360,5 +362,23 @@ public interface MockSettings extends Serializable {
360362
*
361363
* For more information and an elaborate example, see {@link Mockito#lenient()}.
362364
*/
365+
@Deprecated
363366
MockSettings lenient();
367+
368+
/**
369+
* Specifies strictness level for the mock.
370+
* The default strictness level is determined by the rule/runner used.
371+
* If you are using no rule/runner, the default strictness level is LENIENT
372+
*
373+
* <pre class="code"><code class="java">
374+
* Foo defaultStrictMock = mock(Foo.class);
375+
* Foo explicitStrictMock = mock(Foo.class, withSettings().strictness(Strictness.STRICT_STUBS));
376+
* Foo lenientMock = mock(Foo.class, withSettings().strictness(Strictness.LENIENT));
377+
* </code></pre>
378+
*
379+
* @param strictness the strictness level to set on mock
380+
* @return settings instance so that you can fluently specify other settings
381+
* @since 4.6.0
382+
*/
383+
MockSettings strictness(Strictness strictness);
364384
}

src/main/java/org/mockito/Mockito.java

+16
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
* <a href="#49">49. New API for mocking object construction (Since 3.5.0)</a><br/>
106106
* <a href="#50">50. Avoiding code generation when restricting mocks to interfaces (Since 3.12.2)</a><br/>
107107
* <a href="#51">51. New API for marking classes as unmockable (Since 4.1.0)</a><br/>
108+
* <a href="#51">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a><br/>
108109
* </b>
109110
*
110111
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
@@ -1606,6 +1607,21 @@
16061607
* For any class/interface you own that is problematic to mock, you can now mark the class with {@link org.mockito.DoNotMock @DoNotMock}. For usage
16071608
* of the annotation and how to ship your own (to avoid a compile time dependency on a test artifact), please see its JavaDoc.
16081609
* <p>
1610+
*
1611+
* <h3 id="52">52. <a class="meaningful_link" href="#mockito_strictness" name="mockito_strictness">
1612+
* New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a></h3>
1613+
*
1614+
* You can now customize the strictness level for a single mock, either using `@Mock` annotation strictness attribute or
1615+
* using `MockSettings.strictness()`. This can be useful if you want all of your mocks to be strict,
1616+
* but one of the mocks to be lenient.
1617+
*
1618+
* <pre class="code"><code class="java">
1619+
* &#064;Mock(strictness = Strictness.LENIENT)
1620+
* Foo mock;
1621+
* // using MockSettings.withSettings()
1622+
* Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));
1623+
* </code></pre>
1624+
*
16091625
*/
16101626
@CheckReturnValue
16111627
@SuppressWarnings("unchecked")

src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public static Object processAnnotationForMock(
4545
if (annotation.stubOnly()) {
4646
mockSettings.stubOnly();
4747
}
48+
mockSettings.strictness(annotation.strictness());
4849
if (annotation.lenient()) {
4950
mockSettings.lenient();
5051
}

src/main/java/org/mockito/internal/creation/MockSettingsImpl.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static org.mockito.internal.exceptions.Reporter.extraInterfacesRequiresAtLeastOneInterface;
1313
import static org.mockito.internal.exceptions.Reporter.methodDoesNotAcceptParameter;
1414
import static org.mockito.internal.exceptions.Reporter.requiresAtLeastOneListener;
15+
import static org.mockito.internal.exceptions.Reporter.strictnessDoesNotAcceptNullParameter;
1516
import static org.mockito.internal.util.collections.Sets.newSet;
1617

1718
import java.io.Serializable;
@@ -33,6 +34,7 @@
3334
import org.mockito.mock.MockCreationSettings;
3435
import org.mockito.mock.MockName;
3536
import org.mockito.mock.SerializableMode;
37+
import org.mockito.quality.Strictness;
3638
import org.mockito.stubbing.Answer;
3739

3840
@SuppressWarnings("unchecked")
@@ -239,7 +241,16 @@ public <T2> MockCreationSettings<T2> buildStatic(Class<T2> classToMock) {
239241

240242
@Override
241243
public MockSettings lenient() {
242-
this.lenient = true;
244+
this.strictness = Strictness.LENIENT;
245+
return this;
246+
}
247+
248+
@Override
249+
public MockSettings strictness(Strictness strictness) {
250+
this.strictness = strictness;
251+
if (strictness == null) {
252+
throw strictnessDoesNotAcceptNullParameter();
253+
}
243254
return this;
244255
}
245256

src/main/java/org/mockito/internal/creation/settings/CreationSettings.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.mockito.mock.MockCreationSettings;
1919
import org.mockito.mock.MockName;
2020
import org.mockito.mock.SerializableMode;
21+
import org.mockito.quality.Strictness;
2122
import org.mockito.stubbing.Answer;
2223

2324
public class CreationSettings<T> implements MockCreationSettings<T>, Serializable {
@@ -44,7 +45,7 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
4445
private boolean useConstructor;
4546
private Object outerClassInstance;
4647
private Object[] constructorArgs;
47-
protected boolean lenient;
48+
protected Strictness strictness = Strictness.STRICT_STUBS;
4849

4950
public CreationSettings() {}
5051

@@ -65,7 +66,7 @@ public CreationSettings(CreationSettings copy) {
6566
this.useConstructor = copy.isUsingConstructor();
6667
this.outerClassInstance = copy.getOuterClassInstance();
6768
this.constructorArgs = copy.getConstructorArgs();
68-
this.lenient = copy.lenient;
69+
this.strictness = copy.strictness;
6970
this.stripAnnotations = copy.stripAnnotations;
7071
}
7172

@@ -170,6 +171,11 @@ public boolean isStubOnly() {
170171

171172
@Override
172173
public boolean isLenient() {
173-
return lenient;
174+
return strictness == Strictness.LENIENT;
175+
}
176+
177+
@Override
178+
public Strictness getStrictness() {
179+
return strictness;
174180
}
175181
}

src/main/java/org/mockito/internal/exceptions/Reporter.java

+4
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,10 @@ public static MockitoException defaultAnswerDoesNotAcceptNullParameter() {
964964
return new MockitoException("defaultAnswer() does not accept null parameter");
965965
}
966966

967+
public static MockitoException strictnessDoesNotAcceptNullParameter() {
968+
return new MockitoException("strictness() does not accept null parameter");
969+
}
970+
967971
public static MockitoException serializableWontWorkForObjectsThatDontImplementSerializable(
968972
Class<?> classToMock) {
969973
return new MockitoException(

src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class InvocationContainerImpl implements InvocationContainer, Serializabl
3838

3939
public InvocationContainerImpl(MockCreationSettings mockSettings) {
4040
this.registeredInvocations = createRegisteredInvocations(mockSettings);
41-
this.mockStrictness = mockSettings.isLenient() ? Strictness.LENIENT : null;
41+
this.mockStrictness = mockSettings.getStrictness();
4242
this.doAnswerStyleStubbing = new DoAnswerStyleStubbing();
4343
}
4444

src/main/java/org/mockito/internal/stubbing/StrictnessSelector.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public static Strictness determineStrictness(
3131
return stubbing.getStrictness();
3232
}
3333

34-
if (mockSettings.isLenient()) {
35-
return Strictness.LENIENT;
34+
if (mockSettings.getStrictness() != null) {
35+
return mockSettings.getStrictness();
3636
}
3737

3838
return testLevelStrictness;

src/main/java/org/mockito/mock/MockCreationSettings.java

+11
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,21 @@ public interface MockCreationSettings<T> {
118118
Object getOuterClassInstance();
119119

120120
/**
121+
* @deprecated Use {@link MockCreationSettings#getStrictness()} instead.
122+
*
121123
* Informs if the mock was created with "lenient" strictness, e.g. having {@link Strictness#LENIENT} characteristic.
122124
* For more information about using mocks with lenient strictness, see {@link MockSettings#lenient()}.
123125
*
124126
* @since 2.20.0
125127
*/
128+
@Deprecated
126129
boolean isLenient();
130+
131+
/**
132+
* Sets strictness level for the mock, e.g. having {@link Strictness#STRICT_STUBS} characteristic.
133+
* For more information about using mocks with custom strictness, see {@link MockSettings#strictness(Strictness)}.
134+
*
135+
* @since 4.6.0
136+
*/
137+
Strictness getStrictness();
127138
}

src/test/java/org/mockito/internal/creation/MockSettingsImplTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,10 @@ public void addListeners_canAddDuplicateMockObjectListeners_ItsNotOurBusinessThe
260260
assertThat(mockSettingsImpl.getStubbingLookupListeners())
261261
.containsSequence(stubbingLookupListener, stubbingLookupListener);
262262
}
263+
264+
@Test
265+
public void validates_strictness() {
266+
assertThatThrownBy(() -> mockSettingsImpl.strictness(null))
267+
.hasMessageContaining("strictness() does not accept null parameter");
268+
}
263269
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2022 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockitousage.strictness;
6+
7+
import org.assertj.core.api.Assertions;
8+
import org.junit.Rule;
9+
import org.junit.Test;
10+
import org.mockito.Mock;
11+
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
12+
import org.mockito.junit.MockitoJUnit;
13+
import org.mockito.junit.MockitoRule;
14+
import org.mockito.quality.Strictness;
15+
import org.mockitousage.IMethods;
16+
17+
import static org.mockito.Mockito.when;
18+
19+
public class StrictnessMockAnnotationTest {
20+
21+
public @Rule MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
22+
23+
@Mock(strictness = Strictness.LENIENT)
24+
IMethods lenientMock;
25+
26+
@Mock IMethods regularMock;
27+
28+
@Test
29+
public void mock_is_lenient() {
30+
when(lenientMock.simpleMethod("1")).thenReturn("1");
31+
32+
// then lenient mock does not throw:
33+
ProductionCode.simpleMethod(lenientMock, "3");
34+
}
35+
36+
@Test
37+
public void mock_is_strict() {
38+
when(regularMock.simpleMethod("2")).thenReturn("2");
39+
40+
Assertions.assertThatThrownBy(() -> ProductionCode.simpleMethod(regularMock, "4"))
41+
.isInstanceOf(PotentialStubbingProblem.class);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2022 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockitousage.strictness;
6+
7+
import org.assertj.core.api.Assertions;
8+
import org.junit.Before;
9+
import org.junit.Rule;
10+
import org.junit.Test;
11+
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
12+
import org.mockito.junit.MockitoJUnit;
13+
import org.mockito.junit.MockitoRule;
14+
import org.mockito.quality.Strictness;
15+
import org.mockitousage.IMethods;
16+
17+
import static org.mockito.Mockito.*;
18+
19+
public class StrictnessWithSettingsTest {
20+
21+
public @Rule MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
22+
23+
IMethods lenientMock;
24+
IMethods regularMock;
25+
IMethods strictMock;
26+
27+
@Before
28+
public void before() {
29+
lenientMock = mock(IMethods.class, withSettings().strictness(Strictness.LENIENT));
30+
regularMock = mock(IMethods.class);
31+
strictMock = mock(IMethods.class, withSettings().strictness(Strictness.STRICT_STUBS));
32+
}
33+
34+
@Test
35+
public void mock_is_lenient() {
36+
when(lenientMock.simpleMethod("1")).thenReturn("1");
37+
38+
// lenient mock does not throw
39+
ProductionCode.simpleMethod(lenientMock, "3");
40+
}
41+
42+
@Test
43+
public void mock_is_strict_with_default_settings() {
44+
when(regularMock.simpleMethod("3")).thenReturn("3");
45+
46+
Assertions.assertThatThrownBy(() -> ProductionCode.simpleMethod(regularMock, "4"))
47+
.isInstanceOf(PotentialStubbingProblem.class);
48+
}
49+
50+
@Test
51+
public void mock_is_strict_with_explicit_settings() {
52+
when(strictMock.simpleMethod("2")).thenReturn("2");
53+
54+
Assertions.assertThatThrownBy(() -> ProductionCode.simpleMethod(strictMock, "5"))
55+
.isInstanceOf(PotentialStubbingProblem.class);
56+
}
57+
}

0 commit comments

Comments
 (0)