Skip to content

Commit 3e910ea

Browse files
authored
Fixes #2626 : Introduce MockSettings.mockMaker (#2701)
1 parent 0753d48 commit 3e910ea

30 files changed

+638
-63
lines changed

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

+8
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.plugins.MockMaker;
1617
import org.mockito.stubbing.Answer;
1718

1819
/**
@@ -124,6 +125,13 @@
124125
*/
125126
Strictness strictness() default Strictness.TEST_LEVEL_DEFAULT;
126127

128+
/**
129+
* Mock will be created by the given {@link MockMaker}, see {@link MockSettings#mockMaker(String)}.
130+
*
131+
* @since 4.8.0
132+
*/
133+
String mockMaker() default "";
134+
127135
enum Strictness {
128136

129137
/**
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2022 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockito;
6+
7+
import org.mockito.plugins.MockMaker;
8+
9+
/**
10+
* Constants for built-in implementations of {@code MockMaker}.
11+
* You may use the constants of this class for {@link MockSettings#mockMaker(String)} or {@link Mock#mockMaker()}.
12+
* The string values of these constants may also be used in the resource file <code>mockito-extensions/org.mockito.plugins.MockMaker</code>
13+
* as described in the class documentation of {@link MockMaker}.
14+
*
15+
* @since 4.8.0
16+
*/
17+
public final class MockMakers {
18+
/**
19+
* Inline mock maker which can mock final types, enums and final methods.
20+
* This mock maker cannot mock native methods,
21+
* and it does not support {@link MockSettings#extraInterfaces(Class[]) extra interfaces}.
22+
*
23+
* @see <a href="Mockito.html#39">Mocking final types, enums and final methods</a>
24+
*/
25+
public static final String INLINE = "mock-maker-inline";
26+
/**
27+
* Proxy mock maker which avoids code generation, but can only mock interfaces.
28+
*
29+
* @see <a href="Mockito.html#50">Avoiding code generation when restricting mocks to interfaces</a>
30+
*/
31+
public static final String PROXY = "mock-maker-proxy";
32+
/**
33+
* Subclass mock maker which mocks types by creating subclasses.
34+
* This is the first built-in mock maker which has been provided by Mockito.
35+
* Since this mock maker relies on subclasses, it cannot mock final classes and methods.
36+
*/
37+
public static final String SUBCLASS = "mock-maker-subclass";
38+
39+
private MockMakers() {}
40+
}

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

+22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.mockito.listeners.VerificationStartedListener;
1616
import org.mockito.mock.MockCreationSettings;
1717
import org.mockito.mock.SerializableMode;
18+
import org.mockito.plugins.MockMaker;
1819
import org.mockito.quality.Strictness;
1920
import org.mockito.stubbing.Answer;
2021

@@ -381,4 +382,25 @@ public interface MockSettings extends Serializable {
381382
* @since 4.6.0
382383
*/
383384
MockSettings strictness(Strictness strictness);
385+
386+
/**
387+
* Specifies the {@code MockMaker} for the mock.
388+
* The default depends on your project as described in the class documentation of {@link MockMaker}.
389+
* You should usually use the default, this option primarily exists to ease migrations.
390+
* You may specify either one of the constants from {@link MockMakers},
391+
* <pre>
392+
* Object mock = Mockito.mock(Object.class, Mockito.withSettings()
393+
* .mockMaker(MockMakers.INLINE));
394+
* </pre>
395+
* or the {@link Class#getName() binary name} of a class which implements the {@code MockMaker} interface.
396+
* <pre>
397+
* Object mock = Mockito.mock(Object.class, Mockito.withSettings()
398+
* .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
399+
* </pre>
400+
*
401+
* @param mockMaker the {@code MockMaker} to use for the mock
402+
* @return settings instance so that you can fluently specify other settings
403+
* @since 4.8.0
404+
*/
405+
MockSettings mockMaker(String mockMaker);
384406
}

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

+20-3
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
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/>
108+
* <a href="#52">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a><br/>
109+
* <a href="#53">53. Specifying mock maker for individual mocks (Since 4.8.0)</a><br/>
109110
* </b>
110111
*
111112
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
@@ -1586,7 +1587,7 @@
15861587
* released. To define mock behavior and to verify method invocations, use the <code>MockedConstruction</code> that is returned.
15871588
* <p>
15881589
*
1589-
* <h3 id="50">50. <a class="meaningful_link" href="#proxy_mock_maker" name="mocked_construction">Avoiding code generation when only interfaces are mocked</a> (since 3.12.2)</h3>
1590+
* <h3 id="50">50. <a class="meaningful_link" href="#proxy_mock_maker" name="proxy_mock_maker">Avoiding code generation when only interfaces are mocked</a> (since 3.12.2)</h3>
15901591
*
15911592
* The JVM offers the {@link java.lang.reflect.Proxy} facility for creating dynamic proxies of interface types. For most applications, Mockito
15921593
* must be capable of mocking classes as supported by the default mock maker, or even final classes, as supported by the inline mock maker. To
@@ -1609,7 +1610,7 @@
16091610
* <p>
16101611
*
16111612
* <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+
* New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods</a> (Since 4.6.0)</h3>
16131614
*
16141615
* You can now customize the strictness level for a single mock, either using `@Mock` annotation strictness attribute or
16151616
* using `MockSettings.strictness()`. This can be useful if you want all of your mocks to be strict,
@@ -1622,6 +1623,22 @@
16221623
* Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));
16231624
* </code></pre>
16241625
*
1626+
* <h3 id="53">53. <a class="meaningful_link" href="#individual_mock_maker" name="individual_mock_maker">
1627+
* Specifying mock maker for individual mocks</a> (Since 4.8.0)</h3>
1628+
*
1629+
* You may encounter situations where you want to use a different mock maker for a specific test only.
1630+
* For example, you might want to migrate to the <a href="#0.2">inline mock maker</a>, but a few test do not work right away.
1631+
* In such case, you can (temporarily) use {@link MockSettings#mockMaker(String)} and {@link Mock#mockMaker()}
1632+
* to specify the mock maker for a specific mock which is causing the problem.
1633+
*
1634+
* <pre class="code"><code class="java">
1635+
* // using annotation
1636+
* &#064;Mock(mockMaker = MockMakers.SUBCLASS)
1637+
* Foo mock;
1638+
* // using MockSettings.withSettings()
1639+
* Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));
1640+
* </code></pre>
1641+
*
16251642
*/
16261643
@CheckReturnValue
16271644
@SuppressWarnings("unchecked")

src/main/java/org/mockito/internal/MockitoCore.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import static org.mockito.internal.util.MockUtil.getMockHandler;
2323
import static org.mockito.internal.util.MockUtil.isMock;
2424
import static org.mockito.internal.util.MockUtil.resetMock;
25-
import static org.mockito.internal.util.MockUtil.typeMockabilityOf;
2625
import static org.mockito.internal.verification.VerificationModeFactory.noInteractions;
2726
import static org.mockito.internal.verification.VerificationModeFactory.noMoreInteractions;
2827

@@ -31,6 +30,7 @@
3130
import java.util.HashSet;
3231
import java.util.List;
3332
import java.util.Set;
33+
import java.util.function.Function;
3434

3535
import org.mockito.InOrder;
3636
import org.mockito.MockSettings;
@@ -67,21 +67,13 @@
6767
import org.mockito.stubbing.Stubber;
6868
import org.mockito.verification.VerificationMode;
6969

70-
import java.util.Arrays;
71-
import java.util.List;
72-
import java.util.function.Function;
73-
7470
@SuppressWarnings("unchecked")
7571
public class MockitoCore {
7672

7773
private static final DoNotMockEnforcer DO_NOT_MOCK_ENFORCER = Plugins.getDoNotMockEnforcer();
7874
private static final Set<Class<?>> MOCKABLE_CLASSES =
7975
Collections.synchronizedSet(new HashSet<>());
8076

81-
public boolean isTypeMockable(Class<?> typeToMock) {
82-
return typeMockabilityOf(typeToMock).mockable();
83-
}
84-
8577
public <T> T mock(Class<T> typeToMock, MockSettings settings) {
8678
if (!(settings instanceof MockSettingsImpl)) {
8779
throw new IllegalArgumentException(
@@ -160,6 +152,14 @@ public <T> MockedConstruction<T> mockConstruction(
160152
+ "At the moment, you cannot provide your own implementations of that class.");
161153
}
162154
MockSettingsImpl impl = MockSettingsImpl.class.cast(value);
155+
String mockMaker = impl.getMockMaker();
156+
if (mockMaker != null) {
157+
throw new IllegalArgumentException(
158+
"Unexpected MockMaker '"
159+
+ mockMaker
160+
+ "'\n"
161+
+ "At the moment, you cannot override the MockMaker for construction mocks.");
162+
}
163163
return impl.build(typeToMock);
164164
};
165165
MockMaker.ConstructionMockControl<T> control =

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

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public static Object processAnnotationForMock(
5353
if (annotation.strictness() != Mock.Strictness.TEST_LEVEL_DEFAULT) {
5454
mockSettings.strictness(Strictness.valueOf(annotation.strictness().toString()));
5555
}
56+
if (!annotation.mockMaker().isEmpty()) {
57+
mockSettings.mockMaker(annotation.mockMaker());
58+
}
5659

5760
// see @Mock answer default value
5861
mockSettings.defaultAnswer(annotation.answer());

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

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public AutoCloseable process(Class<?> context, Object testInstance) {
8383
}
8484

8585
private static Object spyInstance(Field field, Object instance) {
86+
// TODO: Add mockMaker option for @Spy annotation (#2740)
8687
return Mockito.mock(
8788
instance.getClass(),
8889
withSettings()
@@ -93,6 +94,7 @@ private static Object spyInstance(Field field, Object instance) {
9394

9495
private static Object spyNewInstance(Object testInstance, Field field)
9596
throws InstantiationException, IllegalAccessException, InvocationTargetException {
97+
// TODO: Add mockMaker option for @Spy annotation (#2740)
9698
MockSettings settings =
9799
withSettings().defaultAnswer(CALLS_REAL_METHODS).name(field.getName());
98100
Class<?> type = field.getType();

src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set<Object> m
4242
// B. protect against multiple use of MockitoAnnotations.openMocks()
4343
Mockito.reset(instance);
4444
} else {
45+
// TODO: Add mockMaker option for @Spy annotation (#2740)
4546
Object mock =
4647
Mockito.mock(
4748
instance.getClass(),

src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
package org.mockito.internal.configuration.plugins;
66

77
import java.util.HashMap;
8+
import java.util.HashSet;
89
import java.util.Map;
10+
import java.util.Set;
11+
12+
import org.mockito.MockMakers;
913
import org.mockito.plugins.AnnotationEngine;
1014
import org.mockito.plugins.DoNotMockEnforcer;
1115
import org.mockito.plugins.InstantiatorProvider2;
@@ -16,11 +20,13 @@
1620
import org.mockito.plugins.PluginSwitch;
1721
import org.mockito.plugins.StackTraceCleanerProvider;
1822

19-
class DefaultMockitoPlugins implements MockitoPlugins {
23+
public class DefaultMockitoPlugins implements MockitoPlugins {
2024

2125
private static final Map<String, String> DEFAULT_PLUGINS = new HashMap<>();
22-
static final String INLINE_ALIAS = "mock-maker-inline";
23-
static final String PROXY_ALIAS = "mock-maker-proxy";
26+
static final String INLINE_ALIAS = MockMakers.INLINE;
27+
static final String PROXY_ALIAS = MockMakers.PROXY;
28+
static final String SUBCLASS_ALIAS = MockMakers.SUBCLASS;
29+
public static final Set<String> MOCK_MAKER_ALIASES = new HashSet<>();
2430
static final String MODULE_ALIAS = "member-accessor-module";
2531

2632
static {
@@ -41,6 +47,8 @@ class DefaultMockitoPlugins implements MockitoPlugins {
4147
DEFAULT_PLUGINS.put(
4248
INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker");
4349
DEFAULT_PLUGINS.put(PROXY_ALIAS, "org.mockito.internal.creation.proxy.ProxyMockMaker");
50+
DEFAULT_PLUGINS.put(
51+
SUBCLASS_ALIAS, "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker");
4452
DEFAULT_PLUGINS.put(
4553
MockitoLogger.class.getName(), "org.mockito.internal.util.ConsoleMockitoLogger");
4654
DEFAULT_PLUGINS.put(
@@ -51,6 +59,10 @@ class DefaultMockitoPlugins implements MockitoPlugins {
5159
DEFAULT_PLUGINS.put(
5260
DoNotMockEnforcer.class.getName(),
5361
"org.mockito.internal.configuration.DefaultDoNotMockEnforcer");
62+
63+
MOCK_MAKER_ALIASES.add(INLINE_ALIAS);
64+
MOCK_MAKER_ALIASES.add(PROXY_ALIAS);
65+
MOCK_MAKER_ALIASES.add(SUBCLASS_ALIAS);
5466
}
5567

5668
@Override
@@ -59,7 +71,7 @@ public <T> T getDefaultPlugin(Class<T> pluginType) {
5971
return create(pluginType, className);
6072
}
6173

62-
String getDefaultPluginClass(String classOrAlias) {
74+
public static String getDefaultPluginClass(String classOrAlias) {
6375
return DEFAULT_PLUGINS.get(classOrAlias);
6476
}
6577

src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ class PluginInitializer {
1818

1919
private final PluginSwitch pluginSwitch;
2020
private final Set<String> alias;
21-
private final DefaultMockitoPlugins plugins;
2221

23-
PluginInitializer(PluginSwitch pluginSwitch, Set<String> alias, DefaultMockitoPlugins plugins) {
22+
PluginInitializer(PluginSwitch pluginSwitch, Set<String> alias) {
2423
this.pluginSwitch = pluginSwitch;
2524
this.alias = alias;
26-
this.plugins = plugins;
2725
}
2826

2927
/**
@@ -47,7 +45,7 @@ public <T> T loadImpl(Class<T> service) {
4745
new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));
4846
if (classOrAlias != null) {
4947
if (alias.contains(classOrAlias)) {
50-
classOrAlias = plugins.getDefaultPluginClass(classOrAlias);
48+
classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias);
5149
}
5250
Class<?> pluginClass = loader.loadClass(classOrAlias);
5351
Object plugin = pluginClass.getDeclaredConstructor().newInstance();
@@ -79,7 +77,7 @@ public <T> List<T> loadImpls(Class<T> service) {
7977
List<T> impls = new ArrayList<>();
8078
for (String classOrAlias : classesOrAliases) {
8179
if (alias.contains(classOrAlias)) {
82-
classOrAlias = plugins.getDefaultPluginClass(classOrAlias);
80+
classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias);
8381
}
8482
Class<?> pluginClass = loader.loadClass(classOrAlias);
8583
Object plugin = pluginClass.getDeclaredConstructor().newInstance();

src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ class PluginLoader {
2727
PluginLoader(PluginSwitch pluginSwitch) {
2828
this(
2929
new DefaultMockitoPlugins(),
30-
new PluginInitializer(
31-
pluginSwitch, Collections.emptySet(), new DefaultMockitoPlugins()));
30+
new PluginInitializer(pluginSwitch, Collections.emptySet()));
3231
}
3332

3433
/**
@@ -40,10 +39,7 @@ class PluginLoader {
4039
PluginLoader(PluginSwitch pluginSwitch, String... alias) {
4140
this(
4241
new DefaultMockitoPlugins(),
43-
new PluginInitializer(
44-
pluginSwitch,
45-
new HashSet<>(Arrays.asList(alias)),
46-
new DefaultMockitoPlugins()));
42+
new PluginInitializer(pluginSwitch, new HashSet<>(Arrays.asList(alias))));
4743
}
4844

4945
/**

src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ class PluginRegistry {
2323
private final MockMaker mockMaker =
2424
new PluginLoader(
2525
pluginSwitch,
26-
DefaultMockitoPlugins.INLINE_ALIAS,
27-
DefaultMockitoPlugins.PROXY_ALIAS)
26+
DefaultMockitoPlugins.MOCK_MAKER_ALIASES.toArray(new String[0]))
2827
.loadPlugin(MockMaker.class);
2928

3029
private final MemberAccessor memberAccessor =

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,17 @@ public MockSettings strictness(Strictness strictness) {
254254
return this;
255255
}
256256

257+
@Override
258+
public MockSettings mockMaker(String mockMaker) {
259+
this.mockMaker = mockMaker;
260+
return this;
261+
}
262+
257263
private static <T> CreationSettings<T> validatedSettings(
258264
Class<T> typeToMock, CreationSettings<T> source) {
259265
MockCreationValidator validator = new MockCreationValidator();
260266

261-
validator.validateType(typeToMock);
267+
validator.validateType(typeToMock, source.getMockMaker());
262268
validator.validateExtraInterfaces(typeToMock, source.getExtraInterfaces());
263269
validator.validateMockedType(typeToMock, source.getSpiedInstance());
264270

0 commit comments

Comments
 (0)