Skip to content

Commit

Permalink
Fixes #2626 : Introduce MockSettings.mockMaker
Browse files Browse the repository at this point in the history
  • Loading branch information
JojOatXGME committed Sep 5, 2022
1 parent 0753d48 commit 181d961
Show file tree
Hide file tree
Showing 30 changed files with 638 additions and 63 deletions.
8 changes: 8 additions & 0 deletions src/main/java/org/mockito/Mock.java
Expand Up @@ -13,6 +13,7 @@
import java.lang.annotation.Target;

import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.plugins.MockMaker;
import org.mockito.stubbing.Answer;

/**
Expand Down Expand Up @@ -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 {

/**
Expand Down
40 changes: 40 additions & 0 deletions 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 <code>mockito-extensions/org.mockito.plugins.MockMaker</code>
* 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 <a href="Mockito.html#39">Mocking final types, enums and final methods</a>
*/
public static final String INLINE = "mock-maker-inline";
/**
* Proxy mock maker which avoids code generation, but can only mock interfaces.
*
* @see <a href="Mockito.html#50">Avoiding code generation when restricting mocks to interfaces</a>
*/
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() {}
}
22 changes: 22 additions & 0 deletions src/main/java/org/mockito/MockSettings.java
Expand Up @@ -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;

Expand Down Expand Up @@ -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},
* <pre>
* Object mock = Mockito.mock(Object.class, Mockito.withSettings()
* .mockMaker(MockMakers.INLINE));
* </pre>
* or the {@link Class#getName() binary name} of a class which implements the {@code MockMaker} interface.
* <pre>
* Object mock = Mockito.mock(Object.class, Mockito.withSettings()
* .mockMaker("org.awesome.mockito.AwesomeMockMaker"));
* </pre>
*
* @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);
}
23 changes: 20 additions & 3 deletions src/main/java/org/mockito/Mockito.java
Expand Up @@ -105,7 +105,8 @@
* <a href="#49">49. New API for mocking object construction (Since 3.5.0)</a><br/>
* <a href="#50">50. Avoiding code generation when restricting mocks to interfaces (Since 3.12.2)</a><br/>
* <a href="#51">51. New API for marking classes as unmockable (Since 4.1.0)</a><br/>
* <a href="#51">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a><br/>
* <a href="#52">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a><br/>
* <a href="#53">53. Specifying mock maker for individual mocks (Since 4.8.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 @@ -1586,7 +1587,7 @@
* released. To define mock behavior and to verify method invocations, use the <code>MockedConstruction</code> that is returned.
* <p>
*
* <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>
* <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>
*
* 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
Expand All @@ -1609,7 +1610,7 @@
* <p>
*
* <h3 id="52">52. <a class="meaningful_link" href="#mockito_strictness" name="mockito_strictness">
* New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a></h3>
* New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods</a> (Since 4.6.0)</h3>
*
* 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,
Expand All @@ -1622,6 +1623,22 @@
* Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));
* </code></pre>
*
* <h3 id="53">53. <a class="meaningful_link" href="#individual_mock_maker" name="individual_mock_maker">
* Specifying mock maker for individual mocks</a> (Since 4.8.0)</h3>
*
* 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 <a href="#0.2">inline mock maker</a>, 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.
*
* <pre class="code"><code class="java">
* // using annotation
* &#064;Mock(mockMaker = MockMakers.SUBCLASS)
* Foo mock;
* // using MockSettings.withSettings()
* Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));
* </code></pre>
*
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
Expand Down
18 changes: 9 additions & 9 deletions src/main/java/org/mockito/internal/MockitoCore.java
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -67,21 +67,13 @@
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 {

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

public boolean isTypeMockable(Class<?> typeToMock) {
return typeMockabilityOf(typeToMock).mockable();
}

public <T> T mock(Class<T> typeToMock, MockSettings settings) {
if (!(settings instanceof MockSettingsImpl)) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -160,6 +152,14 @@ public <T> MockedConstruction<T> 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<T> control =
Expand Down
Expand Up @@ -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());
Expand Down
Expand Up @@ -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()
Expand All @@ -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();
Expand Down
Expand Up @@ -42,6 +42,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set<Object> 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(),
Expand Down
Expand Up @@ -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;
Expand All @@ -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<String, String> 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<String> MOCK_MAKER_ALIASES = new HashSet<>();
static final String MODULE_ALIAS = "member-accessor-module";

static {
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -59,7 +71,7 @@ public <T> T getDefaultPlugin(Class<T> pluginType) {
return create(pluginType, className);
}

String getDefaultPluginClass(String classOrAlias) {
public static String getDefaultPluginClass(String classOrAlias) {
return DEFAULT_PLUGINS.get(classOrAlias);
}

Expand Down
Expand Up @@ -18,12 +18,10 @@ class PluginInitializer {

private final PluginSwitch pluginSwitch;
private final Set<String> alias;
private final DefaultMockitoPlugins plugins;

PluginInitializer(PluginSwitch pluginSwitch, Set<String> alias, DefaultMockitoPlugins plugins) {
PluginInitializer(PluginSwitch pluginSwitch, Set<String> alias) {
this.pluginSwitch = pluginSwitch;
this.alias = alias;
this.plugins = plugins;
}

/**
Expand All @@ -47,7 +45,7 @@ public <T> T loadImpl(Class<T> 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();
Expand Down Expand Up @@ -79,7 +77,7 @@ public <T> List<T> loadImpls(Class<T> service) {
List<T> 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();
Expand Down
Expand Up @@ -27,8 +27,7 @@ class PluginLoader {
PluginLoader(PluginSwitch pluginSwitch) {
this(
new DefaultMockitoPlugins(),
new PluginInitializer(
pluginSwitch, Collections.emptySet(), new DefaultMockitoPlugins()));
new PluginInitializer(pluginSwitch, Collections.emptySet()));
}

/**
Expand All @@ -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))));
}

/**
Expand Down
Expand Up @@ -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 =
Expand Down
Expand Up @@ -254,11 +254,17 @@ public MockSettings strictness(Strictness strictness) {
return this;
}

@Override
public MockSettings mockMaker(String mockMaker) {
this.mockMaker = mockMaker;
return this;
}

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

validator.validateType(typeToMock);
validator.validateType(typeToMock, source.getMockMaker());
validator.validateExtraInterfaces(typeToMock, source.getExtraInterfaces());
validator.validateMockedType(typeToMock, source.getSpiedInstance());

Expand Down

0 comments on commit 181d961

Please sign in to comment.