diff --git a/src/main/java/org/mockito/Mock.java b/src/main/java/org/mockito/Mock.java
index e8469b830d..2e4f515035 100644
--- a/src/main/java/org/mockito/Mock.java
+++ b/src/main/java/org/mockito/Mock.java
@@ -13,6 +13,7 @@
import java.lang.annotation.Target;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.plugins.MockMaker;
import org.mockito.stubbing.Answer;
/**
@@ -124,6 +125,13 @@
*/
Strictness strictness() default Strictness.TEST_LEVEL_DEFAULT;
+ /**
+ * Mock will be created by the given {@link MockMaker}, see {@link MockSettings#mockMaker(String)}.
+ *
+ * @since 4.8.0
+ */
+ String mockMaker() default "";
+
enum Strictness {
/**
diff --git a/src/main/java/org/mockito/MockMakers.java b/src/main/java/org/mockito/MockMakers.java
new file mode 100644
index 0000000000..2f5459d222
--- /dev/null
+++ b/src/main/java/org/mockito/MockMakers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito;
+
+import org.mockito.plugins.MockMaker;
+
+/**
+ * Constants for built-in implementations of {@code MockMaker}.
+ * You may use the constants of this class for {@link MockSettings#mockMaker(String)} or {@link Mock#mockMaker()}.
+ * The string values of these constants may also be used in the resource file mockito-extensions/org.mockito.plugins.MockMaker
+ * as described in the class documentation of {@link MockMaker}.
+ *
+ * @since 4.8.0
+ */
+public final class MockMakers {
+ /**
+ * Inline mock maker which can mock final types, enums and final methods.
+ * This mock maker cannot mock native methods,
+ * and it does not support {@link MockSettings#extraInterfaces(Class[]) extra interfaces}.
+ *
+ * @see Mocking final types, enums and final methods
+ */
+ public static final String INLINE = "mock-maker-inline";
+ /**
+ * Proxy mock maker which avoids code generation, but can only mock interfaces.
+ *
+ * @see Avoiding code generation when restricting mocks to interfaces
+ */
+ public static final String PROXY = "mock-maker-proxy";
+ /**
+ * Subclass mock maker which mocks types by creating subclasses.
+ * This is the first built-in mock maker which has been provided by Mockito.
+ * Since this mock maker relies on subclasses, it cannot mock final classes and methods.
+ */
+ public static final String SUBCLASS = "mock-maker-subclass";
+
+ private MockMakers() {}
+}
diff --git a/src/main/java/org/mockito/MockSettings.java b/src/main/java/org/mockito/MockSettings.java
index f838a8d121..8d8a093e25 100644
--- a/src/main/java/org/mockito/MockSettings.java
+++ b/src/main/java/org/mockito/MockSettings.java
@@ -15,6 +15,7 @@
import org.mockito.listeners.VerificationStartedListener;
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.SerializableMode;
+import org.mockito.plugins.MockMaker;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -381,4 +382,25 @@ public interface MockSettings extends Serializable {
* @since 4.6.0
*/
MockSettings strictness(Strictness strictness);
+
+ /**
+ * Specifies the {@code MockMaker} for the mock.
+ * The default depends on your project as described in the class documentation of {@link MockMaker}.
+ * You should usually use the default, this option primarily exists to ease migrations.
+ * You may specify either one of the constants from {@link MockMakers},
+ *
+ * Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+ * .mockMaker(MockMakers.INLINE));
+ *
+ * or the {@link Class#getName() binary name} of a class which implements the {@code MockMaker} interface.
+ *
+ * Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+ * .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
+ *
+ *
+ * @param mockMaker the {@code MockMaker} to use for the mock
+ * @return settings instance so that you can fluently specify other settings
+ * @since 4.8.0
+ */
+ MockSettings mockMaker(String mockMaker);
}
diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java
index 9f7383f232..a221376287 100644
--- a/src/main/java/org/mockito/Mockito.java
+++ b/src/main/java/org/mockito/Mockito.java
@@ -105,7 +105,8 @@
* 49. New API for mocking object construction (Since 3.5.0)
* 50. Avoiding code generation when restricting mocks to interfaces (Since 3.12.2)
* 51. New API for marking classes as unmockable (Since 4.1.0)
- * 52. New strictness attribute for @Mock annotation and MockSettings.strictness()
methods (Since 4.6.0)
+ * 52. New strictness attribute for @Mock annotation and MockSettings.strictness()
methods (Since 4.6.0)
+ * 53. Specifying mock maker for individual mocks (Since 4.8.0)
*
*
*
@@ -1586,7 +1587,7 @@
* released. To define mock behavior and to verify method invocations, use the MockedConstruction
that is returned.
*
*
- *
+ *
*
* The JVM offers the {@link java.lang.reflect.Proxy} facility for creating dynamic proxies of interface types. For most applications, Mockito
* 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 @@
*
*
*
+ * New strictness attribute for @Mock annotation and MockSettings.strictness()
methods (Since 4.6.0)
*
* You can now customize the strictness level for a single mock, either using `@Mock` annotation strictness attribute or
* using `MockSettings.strictness()`. This can be useful if you want all of your mocks to be strict,
@@ -1622,6 +1623,22 @@
* Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));
*
*
+ *
+ *
+ * You may encounter situations where you want to use a different mock maker for a specific test only.
+ * For example, you might want to migrate to the inline mock maker , but a few test do not work right away.
+ * In such case, you can (temporarily) use {@link MockSettings#mockMaker(String)} and {@link Mock#mockMaker()}
+ * to specify the mock maker for a specific mock which is causing the problem.
+ *
+ *
+ * // using annotation
+ * @Mock(mockMaker = MockMakers.SUBCLASS)
+ * Foo mock;
+ * // using MockSettings.withSettings()
+ * Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));
+ *
+ *
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
diff --git a/src/main/java/org/mockito/internal/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java
index fff3b7667e..fd39f6a4a2 100644
--- a/src/main/java/org/mockito/internal/MockitoCore.java
+++ b/src/main/java/org/mockito/internal/MockitoCore.java
@@ -22,7 +22,6 @@
import static org.mockito.internal.util.MockUtil.getMockHandler;
import static org.mockito.internal.util.MockUtil.isMock;
import static org.mockito.internal.util.MockUtil.resetMock;
-import static org.mockito.internal.util.MockUtil.typeMockabilityOf;
import static org.mockito.internal.verification.VerificationModeFactory.noInteractions;
import static org.mockito.internal.verification.VerificationModeFactory.noMoreInteractions;
@@ -31,6 +30,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
import org.mockito.InOrder;
import org.mockito.MockSettings;
@@ -67,10 +67,6 @@
import org.mockito.stubbing.Stubber;
import org.mockito.verification.VerificationMode;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
@SuppressWarnings("unchecked")
public class MockitoCore {
@@ -78,10 +74,6 @@ public class MockitoCore {
private static final Set> MOCKABLE_CLASSES =
Collections.synchronizedSet(new HashSet<>());
- public boolean isTypeMockable(Class> typeToMock) {
- return typeMockabilityOf(typeToMock).mockable();
- }
-
public T mock(Class typeToMock, MockSettings settings) {
if (!(settings instanceof MockSettingsImpl)) {
throw new IllegalArgumentException(
@@ -160,6 +152,14 @@ public MockedConstruction mockConstruction(
+ "At the moment, you cannot provide your own implementations of that class.");
}
MockSettingsImpl impl = MockSettingsImpl.class.cast(value);
+ String mockMaker = impl.getMockMaker();
+ if (mockMaker != null) {
+ throw new IllegalArgumentException(
+ "Unexpected MockMaker '"
+ + mockMaker
+ + "'\n"
+ + "At the moment, you cannot override the MockMaker for construction mocks.");
+ }
return impl.build(typeToMock);
};
MockMaker.ConstructionMockControl control =
diff --git a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
index 66c3f31d12..95356f5c9a 100644
--- a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
+++ b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
@@ -53,6 +53,9 @@ public static Object processAnnotationForMock(
if (annotation.strictness() != Mock.Strictness.TEST_LEVEL_DEFAULT) {
mockSettings.strictness(Strictness.valueOf(annotation.strictness().toString()));
}
+ if (!annotation.mockMaker().isEmpty()) {
+ mockSettings.mockMaker(annotation.mockMaker());
+ }
// see @Mock answer default value
mockSettings.defaultAnswer(annotation.answer());
diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
index a88ad63b84..fa9af798f2 100644
--- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
+++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
@@ -83,6 +83,7 @@ public AutoCloseable process(Class> context, Object testInstance) {
}
private static Object spyInstance(Field field, Object instance) {
+ // TODO: Add mockMaker option for @Spy annotation (#2740)
return Mockito.mock(
instance.getClass(),
withSettings()
@@ -93,6 +94,7 @@ private static Object spyInstance(Field field, Object instance) {
private static Object spyNewInstance(Object testInstance, Field field)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
+ // TODO: Add mockMaker option for @Spy annotation (#2740)
MockSettings settings =
withSettings().defaultAnswer(CALLS_REAL_METHODS).name(field.getName());
Class> type = field.getType();
diff --git a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java
index 73f3004c19..bbfe88b85a 100644
--- a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java
+++ b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java
@@ -42,6 +42,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set m
// B. protect against multiple use of MockitoAnnotations.openMocks()
Mockito.reset(instance);
} else {
+ // TODO: Add mockMaker option for @Spy annotation (#2740)
Object mock =
Mockito.mock(
instance.getClass(),
diff --git a/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java b/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
index 592f79a81e..14f52f3705 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
@@ -5,7 +5,11 @@
package org.mockito.internal.configuration.plugins;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
+
+import org.mockito.MockMakers;
import org.mockito.plugins.AnnotationEngine;
import org.mockito.plugins.DoNotMockEnforcer;
import org.mockito.plugins.InstantiatorProvider2;
@@ -16,11 +20,13 @@
import org.mockito.plugins.PluginSwitch;
import org.mockito.plugins.StackTraceCleanerProvider;
-class DefaultMockitoPlugins implements MockitoPlugins {
+public class DefaultMockitoPlugins implements MockitoPlugins {
private static final Map DEFAULT_PLUGINS = new HashMap<>();
- static final String INLINE_ALIAS = "mock-maker-inline";
- static final String PROXY_ALIAS = "mock-maker-proxy";
+ static final String INLINE_ALIAS = MockMakers.INLINE;
+ static final String PROXY_ALIAS = MockMakers.PROXY;
+ static final String SUBCLASS_ALIAS = MockMakers.SUBCLASS;
+ public static final Set MOCK_MAKER_ALIASES = new HashSet<>();
static final String MODULE_ALIAS = "member-accessor-module";
static {
@@ -41,6 +47,8 @@ class DefaultMockitoPlugins implements MockitoPlugins {
DEFAULT_PLUGINS.put(
INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker");
DEFAULT_PLUGINS.put(PROXY_ALIAS, "org.mockito.internal.creation.proxy.ProxyMockMaker");
+ DEFAULT_PLUGINS.put(
+ SUBCLASS_ALIAS, "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker");
DEFAULT_PLUGINS.put(
MockitoLogger.class.getName(), "org.mockito.internal.util.ConsoleMockitoLogger");
DEFAULT_PLUGINS.put(
@@ -51,6 +59,10 @@ class DefaultMockitoPlugins implements MockitoPlugins {
DEFAULT_PLUGINS.put(
DoNotMockEnforcer.class.getName(),
"org.mockito.internal.configuration.DefaultDoNotMockEnforcer");
+
+ MOCK_MAKER_ALIASES.add(INLINE_ALIAS);
+ MOCK_MAKER_ALIASES.add(PROXY_ALIAS);
+ MOCK_MAKER_ALIASES.add(SUBCLASS_ALIAS);
}
@Override
@@ -59,7 +71,7 @@ public T getDefaultPlugin(Class pluginType) {
return create(pluginType, className);
}
- String getDefaultPluginClass(String classOrAlias) {
+ public static String getDefaultPluginClass(String classOrAlias) {
return DEFAULT_PLUGINS.get(classOrAlias);
}
diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java
index a88f0cc19f..b042a84396 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java
@@ -18,12 +18,10 @@ class PluginInitializer {
private final PluginSwitch pluginSwitch;
private final Set alias;
- private final DefaultMockitoPlugins plugins;
- PluginInitializer(PluginSwitch pluginSwitch, Set alias, DefaultMockitoPlugins plugins) {
+ PluginInitializer(PluginSwitch pluginSwitch, Set alias) {
this.pluginSwitch = pluginSwitch;
this.alias = alias;
- this.plugins = plugins;
}
/**
@@ -47,7 +45,7 @@ public T loadImpl(Class service) {
new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));
if (classOrAlias != null) {
if (alias.contains(classOrAlias)) {
- classOrAlias = plugins.getDefaultPluginClass(classOrAlias);
+ classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias);
}
Class> pluginClass = loader.loadClass(classOrAlias);
Object plugin = pluginClass.getDeclaredConstructor().newInstance();
@@ -79,7 +77,7 @@ public List loadImpls(Class service) {
List impls = new ArrayList<>();
for (String classOrAlias : classesOrAliases) {
if (alias.contains(classOrAlias)) {
- classOrAlias = plugins.getDefaultPluginClass(classOrAlias);
+ classOrAlias = DefaultMockitoPlugins.getDefaultPluginClass(classOrAlias);
}
Class> pluginClass = loader.loadClass(classOrAlias);
Object plugin = pluginClass.getDeclaredConstructor().newInstance();
diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java
index 3f33dffdc6..e554173326 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginLoader.java
@@ -27,8 +27,7 @@ class PluginLoader {
PluginLoader(PluginSwitch pluginSwitch) {
this(
new DefaultMockitoPlugins(),
- new PluginInitializer(
- pluginSwitch, Collections.emptySet(), new DefaultMockitoPlugins()));
+ new PluginInitializer(pluginSwitch, Collections.emptySet()));
}
/**
@@ -40,10 +39,7 @@ class PluginLoader {
PluginLoader(PluginSwitch pluginSwitch, String... alias) {
this(
new DefaultMockitoPlugins(),
- new PluginInitializer(
- pluginSwitch,
- new HashSet<>(Arrays.asList(alias)),
- new DefaultMockitoPlugins()));
+ new PluginInitializer(pluginSwitch, new HashSet<>(Arrays.asList(alias))));
}
/**
diff --git a/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java b/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java
index 01e89d2014..6abaedbaef 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java
@@ -23,8 +23,7 @@ class PluginRegistry {
private final MockMaker mockMaker =
new PluginLoader(
pluginSwitch,
- DefaultMockitoPlugins.INLINE_ALIAS,
- DefaultMockitoPlugins.PROXY_ALIAS)
+ DefaultMockitoPlugins.MOCK_MAKER_ALIASES.toArray(new String[0]))
.loadPlugin(MockMaker.class);
private final MemberAccessor memberAccessor =
diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
index f73a718298..a25299722c 100644
--- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
+++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java
@@ -254,11 +254,17 @@ public MockSettings strictness(Strictness strictness) {
return this;
}
+ @Override
+ public MockSettings mockMaker(String mockMaker) {
+ this.mockMaker = mockMaker;
+ return this;
+ }
+
private static CreationSettings validatedSettings(
Class typeToMock, CreationSettings source) {
MockCreationValidator validator = new MockCreationValidator();
- validator.validateType(typeToMock);
+ validator.validateType(typeToMock, source.getMockMaker());
validator.validateExtraInterfaces(typeToMock, source.getExtraInterfaces());
validator.validateMockedType(typeToMock, source.getSpiedInstance());
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 13939f6fbd..4b50781833 100644
--- a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java
+++ b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java
@@ -46,6 +46,7 @@ public class CreationSettings implements MockCreationSettings, Serializabl
private Object outerClassInstance;
private Object[] constructorArgs;
protected Strictness strictness = null;
+ protected String mockMaker;
public CreationSettings() {}
@@ -68,6 +69,7 @@ public CreationSettings(CreationSettings copy) {
this.constructorArgs = copy.getConstructorArgs();
this.strictness = copy.strictness;
this.stripAnnotations = copy.stripAnnotations;
+ this.mockMaker = copy.mockMaker;
}
@Override
@@ -178,4 +180,9 @@ public boolean isLenient() {
public Strictness getStrictness() {
return strictness;
}
+
+ @Override
+ public String getMockMaker() {
+ return mockMaker;
+ }
}
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java
index fa6a72f53d..8b64a16916 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java
@@ -8,7 +8,6 @@
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
-import org.mockito.internal.MockitoCore;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.util.reflection.GenericMetadataSupport;
import org.mockito.invocation.InvocationOnMock;
@@ -16,8 +15,6 @@
final class RetrieveGenericsForDefaultAnswers {
- private static final MockitoCore MOCKITO_CORE = new MockitoCore();
-
static Object returnTypeForMockWithCorrectGenerics(
InvocationOnMock invocation, AnswerCallback answerCallback) {
Class> type = invocation.getMethod().getReturnType();
@@ -38,7 +35,9 @@ static Object returnTypeForMockWithCorrectGenerics(
}
if (type != null) {
- if (!MOCKITO_CORE.isTypeMockable(type)) {
+ final MockCreationSettings> mockSettings =
+ MockUtil.getMockSettings(invocation.getMock());
+ if (!MockUtil.typeMockabilityOf(type, mockSettings.getMockMaker()).mockable()) {
return null;
}
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 097b11b6e2..8356c1b79b 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java
@@ -5,6 +5,7 @@
package org.mockito.internal.stubbing.defaultanswers;
import static org.mockito.Mockito.withSettings;
+import static org.mockito.internal.util.MockUtil.typeMockabilityOf;
import java.io.IOException;
import java.io.Serializable;
@@ -50,9 +51,10 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
GenericMetadataSupport returnTypeGenericMetadata =
actualParameterizedType(invocation.getMock())
.resolveGenericReturnType(invocation.getMethod());
+ MockCreationSettings> mockSettings = MockUtil.getMockSettings(invocation.getMock());
Class> rawType = returnTypeGenericMetadata.rawType();
- if (!mockitoCore().isTypeMockable(rawType)) {
+ if (!typeMockabilityOf(rawType, mockSettings.getMockMaker()).mockable()) {
if (invocation.getMethod().getReturnType().equals(rawType)) {
return delegate().answer(invocation);
} else {
@@ -119,7 +121,7 @@ private Object newDeepStubMock(
private MockSettings withSettingsUsing(
GenericMetadataSupport returnTypeGenericMetadata,
- MockCreationSettings parentMockSettings) {
+ MockCreationSettings> parentMockSettings) {
MockSettings mockSettings =
returnTypeGenericMetadata.hasRawExtraInterfaces()
? withSettings()
@@ -127,7 +129,8 @@ private MockSettings withSettingsUsing(
: withSettings();
return propagateSerializationSettings(mockSettings, parentMockSettings)
- .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata));
+ .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata))
+ .mockMaker(parentMockSettings.getMockMaker());
}
private MockSettings propagateSerializationSettings(
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java
index 0ef6f0d954..c155780916 100755
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java
@@ -8,7 +8,9 @@
import org.mockito.Mockito;
import org.mockito.internal.creation.MockSettingsImpl;
+import org.mockito.internal.util.MockUtil;
import org.mockito.invocation.InvocationOnMock;
+import org.mockito.mock.MockCreationSettings;
import org.mockito.stubbing.Answer;
public class ReturnsMocks implements Answer, Serializable {
@@ -33,9 +35,14 @@ public Object apply(Class> type) {
return null;
}
+ MockCreationSettings> mockSettings =
+ MockUtil.getMockSettings(invocation.getMock());
+
return Mockito.mock(
type,
- new MockSettingsImpl().defaultAnswer(ReturnsMocks.this));
+ new MockSettingsImpl<>()
+ .defaultAnswer(ReturnsMocks.this)
+ .mockMaker(mockSettings.getMockMaker()));
}
});
}
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java
index bcca2ef52a..a6e0b2c138 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java
@@ -10,9 +10,12 @@
import java.io.Serializable;
import org.mockito.Mockito;
+import org.mockito.internal.creation.MockSettingsImpl;
import org.mockito.internal.debugging.LocationFactory;
+import org.mockito.internal.util.MockUtil;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.invocation.Location;
+import org.mockito.mock.MockCreationSettings;
import org.mockito.stubbing.Answer;
/**
@@ -56,9 +59,16 @@ public Object apply(Class> type) {
return null;
}
+ MockCreationSettings> mockSettings =
+ MockUtil.getMockSettings(invocation.getMock());
+ Answer> defaultAnswer =
+ new ThrowsSmartNullPointer(invocation, LocationFactory.create());
+
return Mockito.mock(
type,
- new ThrowsSmartNullPointer(invocation, LocationFactory.create()));
+ new MockSettingsImpl<>()
+ .defaultAnswer(defaultAnswer)
+ .mockMaker(mockSettings.getMockMaker()));
}
});
}
diff --git a/src/main/java/org/mockito/internal/util/MockCreationValidator.java b/src/main/java/org/mockito/internal/util/MockCreationValidator.java
index 7baa4e252f..9db6b36ea3 100644
--- a/src/main/java/org/mockito/internal/util/MockCreationValidator.java
+++ b/src/main/java/org/mockito/internal/util/MockCreationValidator.java
@@ -18,8 +18,8 @@
@SuppressWarnings("unchecked")
public class MockCreationValidator {
- public void validateType(Class> classToMock) {
- TypeMockability typeMockability = MockUtil.typeMockabilityOf(classToMock);
+ public void validateType(Class> classToMock, String mockMaker) {
+ TypeMockability typeMockability = MockUtil.typeMockabilityOf(classToMock, mockMaker);
if (!typeMockability.mockable()) {
throw cannotMockClass(classToMock, typeMockability.nonMockableReason());
}
diff --git a/src/main/java/org/mockito/internal/util/MockUtil.java b/src/main/java/org/mockito/internal/util/MockUtil.java
index 2159608623..0d80f6e195 100644
--- a/src/main/java/org/mockito/internal/util/MockUtil.java
+++ b/src/main/java/org/mockito/internal/util/MockUtil.java
@@ -7,6 +7,7 @@
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import org.mockito.exceptions.misusing.NotAMockException;
+import org.mockito.internal.configuration.plugins.DefaultMockitoPlugins;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.settings.CreationSettings;
import org.mockito.internal.stubbing.InvocationContainerImpl;
@@ -18,6 +19,9 @@
import org.mockito.plugins.MockMaker.TypeMockability;
import org.mockito.plugins.MockResolver;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import static org.mockito.internal.handler.MockHandlerFactory.createMockHandler;
@@ -25,15 +29,57 @@
@SuppressWarnings("unchecked")
public class MockUtil {
- private static final MockMaker mockMaker = Plugins.getMockMaker();
+ private static final MockMaker defaultMockMaker = Plugins.getMockMaker();
+ private static final Map, MockMaker> mockMakers =
+ new ConcurrentHashMap<>(
+ Collections.singletonMap(defaultMockMaker.getClass(), defaultMockMaker));
private MockUtil() {}
- public static TypeMockability typeMockabilityOf(Class> type) {
- return mockMaker.isTypeMockable(type);
+ private static MockMaker getMockMaker(String mockMaker) {
+ if (mockMaker == null) {
+ return defaultMockMaker;
+ }
+
+ String typeName;
+ if (DefaultMockitoPlugins.MOCK_MAKER_ALIASES.contains(mockMaker)) {
+ typeName = DefaultMockitoPlugins.getDefaultPluginClass(mockMaker);
+ } else {
+ typeName = mockMaker;
+ }
+
+ Class extends MockMaker> type;
+ // Using the context class loader because PluginInitializer.loadImpl is using it as well.
+ // Personally, I am suspicious whether the context class loader is a good choice in either
+ // of these cases.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null) {
+ loader = ClassLoader.getSystemClassLoader();
+ }
+ try {
+ type = loader.loadClass(typeName).asSubclass(MockMaker.class);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to load MockMaker: " + mockMaker, e);
+ }
+
+ return mockMakers.computeIfAbsent(
+ type,
+ t -> {
+ try {
+ return t.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Failed to construct MockMaker: " + t.getName(), e);
+ }
+ });
+ }
+
+ public static TypeMockability typeMockabilityOf(Class> type, String mockMaker) {
+ return getMockMaker(mockMaker).isTypeMockable(type);
}
public static T createMock(MockCreationSettings settings) {
+ MockMaker mockMaker = getMockMaker(settings.getMockMaker());
MockHandler mockHandler = createMockHandler(settings);
Object spiedInstance = settings.getSpiedInstance();
@@ -62,17 +108,11 @@ public static void resetMock(Object mock) {
MockHandler newHandler = createMockHandler(settings);
mock = resolve(mock);
- mockMaker.resetMock(mock, newHandler, settings);
+ getMockMaker(settings.getMockMaker()).resetMock(mock, newHandler, settings);
}
public static MockHandler> getMockHandler(Object mock) {
- if (mock == null) {
- throw new NotAMockException("Argument should be a mock, but is null!");
- }
-
- mock = resolve(mock);
-
- MockHandler handler = mockMaker.getHandler(mock);
+ MockHandler handler = getMockHandlerOrNull(mock);
if (handler != null) {
return handler;
} else {
@@ -104,10 +144,24 @@ public static boolean isMock(Object mock) {
if (mock == null) {
return false;
}
+ return getMockHandlerOrNull(mock) != null;
+ }
+
+ private static MockHandler> getMockHandlerOrNull(Object mock) {
+ if (mock == null) {
+ throw new NotAMockException("Argument should be a mock, but is null!");
+ }
mock = resolve(mock);
- return mockMaker.getHandler(mock) != null;
+ for (MockMaker mockMaker : mockMakers.values()) {
+ MockHandler> handler = mockMaker.getHandler(mock);
+ if (handler != null) {
+ assert getMockMaker(handler.getMockSettings().getMockMaker()) == mockMaker;
+ return handler;
+ }
+ }
+ return null;
}
private static Object resolve(Object mock) {
@@ -143,6 +197,7 @@ public static MockCreationSettings getMockSettings(Object mock) {
public static MockMaker.StaticMockControl createStaticMock(
Class type, MockCreationSettings settings) {
+ MockMaker mockMaker = getMockMaker(settings.getMockMaker());
MockHandler handler = createMockHandler(settings);
return mockMaker.createStaticMock(type, settings, handler);
}
@@ -153,11 +208,13 @@ public static MockMaker.ConstructionMockControl createConstructionMock(
MockedConstruction.MockInitializer mockInitializer) {
Function> handlerFactory =
context -> createMockHandler(settingsFactory.apply(context));
- return mockMaker.createConstructionMock(
+ return defaultMockMaker.createConstructionMock(
type, settingsFactory, handlerFactory, mockInitializer);
}
public static void clearAllCaches() {
- mockMaker.clearAllCaches();
+ for (MockMaker mockMaker : mockMakers.values()) {
+ mockMaker.clearAllCaches();
+ }
}
}
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java
index 3f104d1f02..7d51a6bb1b 100644
--- a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java
+++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java
@@ -259,7 +259,11 @@ public int compare(Constructor> constructorA, Constructor> constructorB) {
private int countMockableParams(Constructor> constructor) {
int constructorMockableParamsSize = 0;
for (Class> aClass : constructor.getParameterTypes()) {
- if (MockUtil.typeMockabilityOf(aClass).mockable()) {
+ // The argResolver already knows the concrete types it can provide.
+ // Instead of checking for mockability, I think it would be better to
+ // ask the argResolver whether it can resolve this type.
+ // Anyway, I keep it for now to avoid breaking any existing code.
+ if (MockUtil.typeMockabilityOf(aClass, null).mockable()) {
constructorMockableParamsSize++;
}
}
diff --git a/src/main/java/org/mockito/mock/MockCreationSettings.java b/src/main/java/org/mockito/mock/MockCreationSettings.java
index f7f0b96028..94c74558ee 100644
--- a/src/main/java/org/mockito/mock/MockCreationSettings.java
+++ b/src/main/java/org/mockito/mock/MockCreationSettings.java
@@ -12,6 +12,7 @@
import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.StubbingLookupListener;
import org.mockito.listeners.VerificationStartedListener;
+import org.mockito.plugins.MockMaker;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -135,4 +136,13 @@ public interface MockCreationSettings {
* @since 4.6.0
*/
Strictness getStrictness();
+
+ /**
+ * Returns the {@link MockMaker} which shall be used to create the mock.
+ * When the return value is {@code null}, the default shall be used.
+ *
+ * @see MockSettings#mockMaker(String)
+ * @since 4.8.0
+ */
+ String getMockMaker();
}
diff --git a/src/main/java/org/mockito/plugins/MockMaker.java b/src/main/java/org/mockito/plugins/MockMaker.java
index 93a87ef0a5..c0b1cbcd2d 100644
--- a/src/main/java/org/mockito/plugins/MockMaker.java
+++ b/src/main/java/org/mockito/plugins/MockMaker.java
@@ -4,6 +4,7 @@
*/
package org.mockito.plugins;
+import org.mockito.MockSettings;
import org.mockito.MockedConstruction;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.invocation.MockHandler;
@@ -45,6 +46,19 @@
* Note that if several mockito-extensions/org.mockito.plugins.MockMaker
files exists in the classpath
* Mockito will only use the first returned by the standard {@link ClassLoader#getResource} mechanism.
*
+ *
Using the MockSettings of individual mocks
+ *
+ * If you want to use a {@code MockMaker} only for a specific mock,
+ * you can specify it using {@link MockSettings#mockMaker(String)}.
+ *
+ * // Use a built-in mock maker
+ * Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+ * .mockMaker(MockMakers.INLINE));
+ * // Or load a mock maker using a fully qualified class name
+ * Object mock = Mockito.mock(Object.class, Mockito.withSettings()
+ * .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
+ *
+ *
* @see org.mockito.mock.MockCreationSettings
* @see org.mockito.invocation.MockHandler
* @since 1.9.5
diff --git a/src/test/java/org/mockito/ProgrammaticMockMakerTest.java b/src/test/java/org/mockito/ProgrammaticMockMakerTest.java
new file mode 100644
index 0000000000..f03555d0e5
--- /dev/null
+++ b/src/test/java/org/mockito/ProgrammaticMockMakerTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2022 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.withSettings;
+
+import org.junit.Test;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.exceptions.verification.SmartNullPointerException;
+import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+
+public final class ProgrammaticMockMakerTest {
+ @Test
+ public void test_normal_mock_uses_given_mock_maker() {
+ ClassWithFinalMethod inlineMock =
+ Mockito.mock(
+ ClassWithFinalMethod.class, withSettings().mockMaker(MockMakers.INLINE));
+ ClassWithFinalMethod subclassMock =
+ Mockito.mock(
+ ClassWithFinalMethod.class, withSettings().mockMaker(MockMakers.SUBCLASS));
+
+ Mockito.when(inlineMock.finalMethodCallingNonFinal()).thenReturn("MOCKED");
+ Mockito.when(subclassMock.finalMethodCallingNonFinal()).thenReturn("MOCKED");
+
+ assertEquals("MOCKED", inlineMock.finalMethodCallingNonFinal());
+ assertEquals("ORIGINAL", subclassMock.finalMethodCallingNonFinal());
+ assertEquals("MOCKED", subclassMock.nonFinal());
+ }
+
+ @Test
+ public void test_mockability_check_uses_given_mock_maker() {
+ assertNotNull(Mockito.mock(FinalClass.class, withSettings().mockMaker(MockMakers.INLINE)));
+ assertThrows(
+ MockitoException.class,
+ () ->
+ Mockito.mock(
+ FinalClass.class, withSettings().mockMaker(MockMakers.SUBCLASS)));
+ }
+
+ @Test
+ public void test_deep_stups_inherit_mock_maker() {
+ Container inlineMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.INLINE)
+ .defaultAnswer(Answers.RETURNS_DEEP_STUBS));
+ Container subclassMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.SUBCLASS)
+ .defaultAnswer(Answers.RETURNS_DEEP_STUBS));
+
+ assertNotNull(inlineMock.finalClass());
+ assertNotNull(inlineMock.subContainer().finalClass());
+ assertNull(inlineMock.finalClass().someMethod());
+ assertNull(inlineMock.subContainer().finalClass().someMethod());
+ assertNull(inlineMock.classWithFinalMethod().finalMethod());
+ assertNull(inlineMock.subContainer().classWithFinalMethod().finalMethod());
+
+ assertNull(subclassMock.finalClass());
+ assertNull(subclassMock.subContainer().finalClass());
+ assertEquals("ORIGINAL", subclassMock.classWithFinalMethod().finalMethod());
+ assertEquals("ORIGINAL", subclassMock.subContainer().classWithFinalMethod().finalMethod());
+ }
+
+ @Test
+ public void test_returned_mocks_inherit_mock_maker() {
+ Container inlineMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.INLINE)
+ .defaultAnswer(Answers.RETURNS_MOCKS));
+ Container subclassMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.SUBCLASS)
+ .defaultAnswer(Answers.RETURNS_MOCKS));
+
+ assertNotNull(inlineMock.finalClass());
+ assertNotNull(inlineMock.subContainer().finalClass());
+ assertEquals("", inlineMock.finalClass().someMethod());
+ assertEquals("", inlineMock.subContainer().finalClass().someMethod());
+ assertEquals("", inlineMock.classWithFinalMethod().finalMethod());
+ assertEquals("", inlineMock.subContainer().classWithFinalMethod().finalMethod());
+
+ assertNull(subclassMock.finalClass());
+ assertNull(subclassMock.subContainer().finalClass());
+ assertEquals("ORIGINAL", subclassMock.classWithFinalMethod().finalMethod());
+ assertEquals("ORIGINAL", subclassMock.subContainer().classWithFinalMethod().finalMethod());
+ }
+
+ @Test
+ public void test_smart_nulls_inherit_mock_maker() {
+ Container inlineMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.INLINE)
+ .defaultAnswer(Answers.RETURNS_SMART_NULLS));
+ Container subclassMock =
+ Mockito.mock(
+ Container.class,
+ withSettings()
+ .mockMaker(MockMakers.SUBCLASS)
+ .defaultAnswer(Answers.RETURNS_SMART_NULLS));
+
+ assertNotNull(inlineMock.finalClass());
+ assertNotNull(inlineMock.classWithFinalMethod());
+ assertThrows(SmartNullPointerException.class, () -> inlineMock.finalClass().someMethod());
+ assertThrows(
+ SmartNullPointerException.class,
+ () -> inlineMock.classWithFinalMethod().finalMethod());
+
+ assertNull(subclassMock.finalClass());
+ assertNotNull(subclassMock.classWithFinalMethod());
+ assertEquals("ORIGINAL", subclassMock.classWithFinalMethod().finalMethod());
+ }
+
+ @Test
+ public void test_custom_mock_maker() {
+ assertThatThrownBy(
+ () -> {
+ Mockito.mock(
+ Container.class,
+ withSettings().mockMaker(CustomMockMaker.class.getName()));
+ })
+ .hasMessage("CUSTOM MOCK MAKER");
+ }
+
+ @Test
+ public void test_exception_when_mock_maker_cannot_be_instantiated() {
+ class InvalidMockMaker extends SubclassByteBuddyMockMaker {
+ // Local classes have an implicit constructor parameter,
+ // which makes them an invalid mock maker.
+ }
+ assertThatThrownBy(
+ () -> {
+ Mockito.mock(
+ Container.class,
+ withSettings().mockMaker(InvalidMockMaker.class.getName()));
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Failed to construct MockMaker")
+ .hasMessageContaining(InvalidMockMaker.class.getName());
+ }
+
+ private static final class FinalClass {
+ String someMethod() {
+ return "ORIGINAL";
+ }
+ }
+
+ private static class ClassWithFinalMethod {
+ final String finalMethod() {
+ return "ORIGINAL";
+ }
+
+ final String finalMethodCallingNonFinal() {
+ nonFinal();
+ return "ORIGINAL";
+ }
+
+ String nonFinal() {
+ return "ORIGINAL";
+ }
+ }
+
+ private static class Container {
+ FinalClass finalClass() {
+ return new FinalClass();
+ }
+
+ ClassWithFinalMethod classWithFinalMethod() {
+ return new ClassWithFinalMethod();
+ }
+
+ Container subContainer() {
+ return new Container();
+ }
+ }
+
+ public static class CustomMockMaker extends SubclassByteBuddyMockMaker {
+ @Override
+ public T createMock(MockCreationSettings settings, MockHandler handler) {
+ throw new RuntimeException("CUSTOM MOCK MAKER");
+ }
+ }
+}
diff --git a/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java b/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java
index 44afe0b6a9..aa1835696e 100644
--- a/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java
+++ b/src/test/java/org/mockito/internal/configuration/plugins/DefaultMockitoPluginsTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.*;
import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.INLINE_ALIAS;
import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.PROXY_ALIAS;
+import static org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.SUBCLASS_ALIAS;
import org.junit.Test;
import org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker;
@@ -25,11 +26,14 @@ public class DefaultMockitoPluginsTest extends TestBase {
public void provides_plugins() throws Exception {
assertEquals(
"org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker",
- plugins.getDefaultPluginClass(INLINE_ALIAS));
+ DefaultMockitoPlugins.getDefaultPluginClass(INLINE_ALIAS));
assertEquals(InlineByteBuddyMockMaker.class, plugins.getInlineMockMaker().getClass());
assertEquals(
"org.mockito.internal.creation.proxy.ProxyMockMaker",
- plugins.getDefaultPluginClass(PROXY_ALIAS));
+ DefaultMockitoPlugins.getDefaultPluginClass(PROXY_ALIAS));
+ assertEquals(
+ "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker",
+ DefaultMockitoPlugins.getDefaultPluginClass(SUBCLASS_ALIAS));
assertEquals(
ByteBuddyMockMaker.class, plugins.getDefaultPlugin(MockMaker.class).getClass());
assertNotNull(plugins.getDefaultPlugin(InstantiatorProvider2.class));
diff --git a/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java b/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java
index 7efffcf6a2..7491a3f070 100644
--- a/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java
+++ b/src/test/java/org/mockito/internal/util/MockCreationValidatorTest.java
@@ -66,7 +66,7 @@ public void should_validation_be_safe_when_nulls_passed() {
@Test
public void should_fail_when_type_not_mockable() {
try {
- validator.validateType(long.class);
+ validator.validateType(long.class, null);
} catch (MockitoException ex) {
assertThat(ex.getMessage()).contains("primitive");
}
diff --git a/src/test/java/org/mockito/internal/util/MockUtilTest.java b/src/test/java/org/mockito/internal/util/MockUtilTest.java
index 50cadb4c0c..834178cdec 100644
--- a/src/test/java/org/mockito/internal/util/MockUtilTest.java
+++ b/src/test/java/org/mockito/internal/util/MockUtilTest.java
@@ -100,12 +100,12 @@ interface SomeInterface {}
@Test
public void should_know_if_type_is_mockable() throws Exception {
- Assertions.assertThat(MockUtil.typeMockabilityOf(FinalClass.class).mockable())
+ Assertions.assertThat(MockUtil.typeMockabilityOf(FinalClass.class, null).mockable())
.isEqualTo(Plugins.getMockMaker().isTypeMockable(FinalClass.class).mockable());
- assertFalse(MockUtil.typeMockabilityOf(int.class).mockable());
+ assertFalse(MockUtil.typeMockabilityOf(int.class, null).mockable());
- assertTrue(MockUtil.typeMockabilityOf(SomeClass.class).mockable());
- assertTrue(MockUtil.typeMockabilityOf(SomeInterface.class).mockable());
+ assertTrue(MockUtil.typeMockabilityOf(SomeClass.class, null).mockable());
+ assertTrue(MockUtil.typeMockabilityOf(SomeInterface.class, null).mockable());
}
}
diff --git a/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java b/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java
new file mode 100644
index 0000000000..63c0d761bd
--- /dev/null
+++ b/src/test/java/org/mockitointegration/DeferMockMakersClassLoadingTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2019 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitointegration;
+
+import static org.mockito.Mockito.withSettings;
+import static org.mockitoutil.ClassLoaders.coverageTool;
+
+import java.lang.reflect.Method;
+
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker;
+import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker;
+import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker;
+import org.mockito.internal.creation.proxy.ProxyMockMaker;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.plugins.MockMaker;
+import org.mockitoutil.ClassLoaders;
+
+public class DeferMockMakersClassLoadingTest {
+ private static final Object MY_MOCK = new Object();
+
+ @Test
+ public void mockito_should_not_load_mock_makers_it_does_not_need() throws Exception {
+ ClassLoader classLoader_without_mockMakers =
+ ClassLoaders.excludingClassLoader()
+ .withCodeSourceUrlOf(
+ Mockito.class,
+ Matcher.class,
+ CustomMockMaker.class,
+ Assertions.class)
+ .withCodeSourceUrlOf(coverageTool())
+ .without(
+ ByteBuddyMockMaker.class.getName(),
+ SubclassByteBuddyMockMaker.class.getName(),
+ InlineByteBuddyMockMaker.class.getName(),
+ ProxyMockMaker.class.getName())
+ .build();
+
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(classLoader_without_mockMakers);
+ try {
+ Class> self = classLoader_without_mockMakers.loadClass(getClass().getName());
+ Method createMock = self.getMethod("createMock");
+ createMock.invoke(null);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+
+ // Called by reflection from the test method
+ public static void createMock() {
+ Assertions.assertThat(
+ Mockito.mock(
+ Object.class,
+ withSettings().mockMaker(CustomMockMaker.class.getName())))
+ .isSameAs(MY_MOCK);
+ }
+
+ public static class CustomMockMaker implements MockMaker {
+ @Override
+ public T createMock(MockCreationSettings settings, MockHandler handler) {
+ return settings.getTypeToMock().cast(MY_MOCK);
+ }
+
+ @Override
+ public MockHandler getHandler(Object mock) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public TypeMockability isTypeMockable(Class> type) {
+ return new TypeMockability() {
+ @Override
+ public boolean mockable() {
+ return type.equals(Object.class);
+ }
+
+ @Override
+ public String nonMockableReason() {
+ return mockable() ? "" : "type != Object.class";
+ }
+ };
+ }
+ }
+}
diff --git a/src/test/java/org/mockitousage/annotation/ProgrammaticMockMakerAnnotationTest.java b/src/test/java/org/mockitousage/annotation/ProgrammaticMockMakerAnnotationTest.java
new file mode 100644
index 0000000000..ad5f2a50ae
--- /dev/null
+++ b/src/test/java/org/mockitousage/annotation/ProgrammaticMockMakerAnnotationTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitousage.annotation;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockMakers;
+import org.mockito.Mockito;
+import org.mockitoutil.TestBase;
+
+public class ProgrammaticMockMakerAnnotationTest extends TestBase {
+ @Mock(mockMaker = MockMakers.INLINE)
+ ClassWithFinalMethod inlineMock;
+
+ @Mock(mockMaker = MockMakers.SUBCLASS)
+ ClassWithFinalMethod subclassMock;
+
+ @Test
+ public void test_mock_uses_given_mock_maker() {
+ Mockito.when(inlineMock.finalMethodCallingNonFinal()).thenReturn("MOCKED");
+ Mockito.when(subclassMock.finalMethodCallingNonFinal()).thenReturn("MOCKED");
+
+ assertEquals("MOCKED", inlineMock.finalMethodCallingNonFinal());
+ assertEquals("ORIGINAL", subclassMock.finalMethodCallingNonFinal());
+ assertEquals("MOCKED", subclassMock.nonFinal());
+ }
+
+ private static class ClassWithFinalMethod {
+ final String finalMethodCallingNonFinal() {
+ nonFinal();
+ return "ORIGINAL";
+ }
+
+ String nonFinal() {
+ return "ORIGINAL";
+ }
+ }
+}
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java
index 7eb87c5702..adbe3fe3ee 100644
--- a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java
+++ b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java
@@ -11,11 +11,13 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
+import org.mockito.MockMakers;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoException;
@@ -154,6 +156,21 @@ public void testConstructionMockMustNotTargetAbstractClass() {
.hasMessageContaining("It is not possible to construct primitive types or abstract types");
}
+ @Test
+ public void testConstructionMocksMustNotUseCustomMockMaker() {
+ assertThatThrownBy(
+ () -> {
+ try (MockedConstruction ignored = Mockito.mockConstruction(
+ Dummy.class,
+ withSettings().mockMaker(MockMakers.INLINE))
+ ) {
+ new Dummy();
+ }
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("you cannot override the MockMaker for construction mocks");
+ }
+
static class Dummy {