Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2626 : Introduce MockSettings.mockMaker #2701

Merged
merged 1 commit into from Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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