diff --git a/src/main/java/org/mockito/MockedConstruction.java b/src/main/java/org/mockito/MockedConstruction.java
new file mode 100644
index 0000000000..c00fb021a2
--- /dev/null
+++ b/src/main/java/org/mockito/MockedConstruction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito;
+
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * Represents a mock of any object construction of the represented type. Within the scope of the
+ * mocked construction, the invocation of any interceptor will generate a mock which will be
+ * prepared as specified when generating this scope. The mock can also be received via this
+ * instance.
+ *
+ * If the {@link Mock} annotation is used on fields or method parameters of this type, a mocked
+ * construction is created instead of a regular mock. The mocked construction is activated and
+ * released upon completing any relevant test.
+ *
+ * @param The type for which the construction is being mocked.
+ */
+@Incubating
+public interface MockedConstruction extends ScopedMock {
+
+ List constructed();
+
+ interface Context {
+
+ int getCount();
+
+ Constructor> constructor();
+
+ List> arguments();
+ }
+
+ interface MockInitializer {
+
+ void prepare(T mock, Context context) throws Throwable;
+ }
+}
diff --git a/src/main/java/org/mockito/MockedStatic.java b/src/main/java/org/mockito/MockedStatic.java
index fc23c90776..ac291ba33c 100644
--- a/src/main/java/org/mockito/MockedStatic.java
+++ b/src/main/java/org/mockito/MockedStatic.java
@@ -24,7 +24,7 @@
* @param The type being mocked.
*/
@Incubating
-public interface MockedStatic extends AutoCloseable {
+public interface MockedStatic extends ScopedMock {
/**
* See {@link Mockito#when(Object)}.
@@ -63,24 +63,6 @@ default void verify(Verification verification) {
*/
void verifyNoInteractions();
- /**
- * Checks if this mock is closed.
- *
- * @return {@code true} if this mock is closed.
- */
- boolean isClosed();
-
- /**
- * Releases this static mock and throws a {@link org.mockito.exceptions.base.MockitoException} if closed already.
- */
- @Override
- void close();
-
- /**
- * Releases this static mock and is non-operational if already released.
- */
- void closeOnDemand();
-
interface Verification {
void apply() throws Throwable;
diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java
index 6f6560d2c2..64df8b8db7 100644
--- a/src/main/java/org/mockito/Mockito.java
+++ b/src/main/java/org/mockito/Mockito.java
@@ -28,18 +28,10 @@
import org.mockito.quality.Strictness;
import org.mockito.session.MockitoSessionBuilder;
import org.mockito.session.MockitoSessionLogger;
-import org.mockito.stubbing.Answer;
-import org.mockito.stubbing.Answer1;
-import org.mockito.stubbing.LenientStubber;
-import org.mockito.stubbing.OngoingStubbing;
-import org.mockito.stubbing.Stubber;
-import org.mockito.stubbing.Stubbing;
-import org.mockito.stubbing.VoidAnswer1;
-import org.mockito.verification.After;
-import org.mockito.verification.Timeout;
-import org.mockito.verification.VerificationAfterDelay;
-import org.mockito.verification.VerificationMode;
-import org.mockito.verification.VerificationWithTimeout;
+import org.mockito.stubbing.*;
+import org.mockito.verification.*;
+
+import java.util.function.Function;
/**
*
@@ -106,6 +98,7 @@
* 46. New Mockito.lenient()
and MockSettings.lenient()
methods (Since 2.20.0)
* 47. New API for clearing mock state in inline mocking (Since 2.25.0)
* 48. New API for mocking static methods (Since 3.4.0)
+ * 49. New API for mocking object construction (Since 3.5.0)
*
*
*
@@ -1565,9 +1558,32 @@
* assertEquals("foo", Foo.method());
*
*
- * Due to the defined scope of the static mock, it returns to its original behvior once the scope is released. To define mock
+ * Due to the defined scope of the static mock, it returns to its original behavior once the scope is released. To define mock
* behavior and to verify static method invocations, use the MockedStatic
that is returned.
*
+ *
+ *
+ *
+ * When using the inline mock maker , it is possible to generate mocks on constructor invocations within the current
+ * thread and a user-defined scope. This way, Mockito assures that concurrently and sequentially running tests do not interfere.
+ *
+ * To make sure a constructor mocks remain temporary, it is recommended to define the scope within a try-with-resources construct.
+ * In the following example, the Foo
type's construction would generate a mock:
+ *
+ *
+ * assertEquals("foo", Foo.method());
+ * try (MockedConstruction mocked = mockConstruction(Foo.class)) {
+ * Foo foo = new Foo();
+ * when(foo.method()).thenReturn("bar");
+ * assertEquals("bar", foo.method());
+ * verify(foo).method();
+ * }
+ * assertEquals("foo", foo.method());
+ *
+ *
+ * Due to the defined scope of the mocked construction, object construction returns to its original behavior once the scope is
+ * released. To define mock behavior and to verify static method invocations, use the MockedConstruction
that is returned.
+ *
*/
@SuppressWarnings("unchecked")
public class Mockito extends ArgumentMatchers {
@@ -2144,6 +2160,152 @@ public static MockedStatic mockStatic(Class classToMock, MockSettings
return MOCKITO_CORE.mockStatic(classToMock, mockSettings);
}
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param defaultAnswer the default answer for the first created mock.
+ * @param additionalAnswers the default answer for all additional mocks. For any access mocks, the
+ * last answer is used. If this array is empty, the {@code defaultAnswer} is used.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstructionWithAnswer(
+ Class classToMock, Answer defaultAnswer, Answer... additionalAnswers) {
+ return mockConstruction(
+ classToMock,
+ context -> {
+ if (context.getCount() == 1 || additionalAnswers.length == 0) {
+ return withSettings().defaultAnswer(defaultAnswer);
+ } else if (context.getCount() >= additionalAnswers.length) {
+ return withSettings()
+ .defaultAnswer(additionalAnswers[additionalAnswers.length - 1]);
+ } else {
+ return withSettings()
+ .defaultAnswer(additionalAnswers[context.getCount() - 2]);
+ }
+ },
+ (mock, context) -> {});
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(Class classToMock) {
+ return mockConstruction(classToMock, index -> withSettings(), (mock, context) -> {});
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param mockInitializer a callback to prepare a mock's methods after its instantiation.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(
+ Class classToMock, MockedConstruction.MockInitializer mockInitializer) {
+ return mockConstruction(classToMock, withSettings(), mockInitializer);
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param mockSettings the mock settings to use.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(
+ Class classToMock, MockSettings mockSettings) {
+ return mockConstruction(classToMock, context -> mockSettings);
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param mockSettingsFactory the mock settings to use.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(
+ Class classToMock,
+ Function mockSettingsFactory) {
+ return mockConstruction(classToMock, mockSettingsFactory, (mock, context) -> {});
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param mockSettings the settings to use.
+ * @param mockInitializer a callback to prepare a mock's methods after its instantiation.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(
+ Class classToMock,
+ MockSettings mockSettings,
+ MockedConstruction.MockInitializer mockInitializer) {
+ return mockConstruction(classToMock, index -> mockSettings, mockInitializer);
+ }
+
+ /**
+ * Creates a thread-local mock controller for all constructions of the given class.
+ * The returned object's {@link MockedConstruction#close()} method must be called upon completing the
+ * test or the mock will remain active on the current thread.
+ *
+ * See examples in javadoc for {@link Mockito} class
+ *
+ * @param classToMock non-abstract class of which constructions should be mocked.
+ * @param mockSettingsFactory a function to create settings to use.
+ * @param mockInitializer a callback to prepare a mock's methods after its instantiation.
+ * @return mock controller
+ */
+ @Incubating
+ @CheckReturnValue
+ public static MockedConstruction mockConstruction(
+ Class classToMock,
+ Function mockSettingsFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ return MOCKITO_CORE.mockConstruction(classToMock, mockSettingsFactory, mockInitializer);
+ }
+
/**
* Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
*
diff --git a/src/main/java/org/mockito/ScopedMock.java b/src/main/java/org/mockito/ScopedMock.java
new file mode 100644
index 0000000000..3ef0d9b3d3
--- /dev/null
+++ b/src/main/java/org/mockito/ScopedMock.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito;
+
+/**
+ * Represents a mock with a thread-local explicit scope. Scoped mocks must be closed by the entity
+ * that activates the scoped mock.
+ */
+@Incubating
+public interface ScopedMock extends AutoCloseable {
+
+ /**
+ * Checks if this mock is closed.
+ *
+ * @return {@code true} if this mock is closed.
+ */
+ boolean isClosed();
+
+ /**
+ * Closes this scoped mock and throws an exception if already closed.
+ */
+ @Override
+ void close();
+
+ /**
+ * Releases this scoped mock and is non-operational if already released.
+ */
+ void closeOnDemand();
+}
diff --git a/src/main/java/org/mockito/creation/instance/InstantiationException.java b/src/main/java/org/mockito/creation/instance/InstantiationException.java
index 1cfbaba23f..34548d5eb8 100644
--- a/src/main/java/org/mockito/creation/instance/InstantiationException.java
+++ b/src/main/java/org/mockito/creation/instance/InstantiationException.java
@@ -13,6 +13,13 @@
*/
public class InstantiationException extends MockitoException {
+ /**
+ * @since 3.5.0
+ */
+ public InstantiationException(String message) {
+ super(message);
+ }
+
/**
* @since 2.15.4
*/
diff --git a/src/main/java/org/mockito/internal/MockedConstructionImpl.java b/src/main/java/org/mockito/internal/MockedConstructionImpl.java
new file mode 100644
index 0000000000..ee09f3ccde
--- /dev/null
+++ b/src/main/java/org/mockito/internal/MockedConstructionImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.mockito.MockedConstruction;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.debugging.LocationImpl;
+import org.mockito.invocation.Location;
+import org.mockito.plugins.MockMaker;
+
+import static org.mockito.internal.util.StringUtil.*;
+
+public final class MockedConstructionImpl implements MockedConstruction {
+
+ private final MockMaker.ConstructionMockControl control;
+
+ private boolean closed;
+
+ private final Location location = new LocationImpl();
+
+ protected MockedConstructionImpl(MockMaker.ConstructionMockControl control) {
+ this.control = control;
+ }
+
+ @Override
+ public List constructed() {
+ return Collections.unmodifiableList(control.getMocks());
+ }
+
+ @Override
+ public boolean isClosed() {
+ return closed;
+ }
+
+ @Override
+ public void close() {
+ assertNotClosed();
+
+ closed = true;
+ control.disable();
+ }
+
+ @Override
+ public void closeOnDemand() {
+ if (!closed) {
+ close();
+ }
+ }
+
+ private void assertNotClosed() {
+ if (closed) {
+ throw new MockitoException(
+ join(
+ "The static mock created at",
+ location.toString(),
+ "is already resolved and cannot longer be used"));
+ }
+ }
+}
diff --git a/src/main/java/org/mockito/internal/MockedStaticImpl.java b/src/main/java/org/mockito/internal/MockedStaticImpl.java
index 25434769e2..cb7794cc03 100644
--- a/src/main/java/org/mockito/internal/MockedStaticImpl.java
+++ b/src/main/java/org/mockito/internal/MockedStaticImpl.java
@@ -4,8 +4,6 @@
*/
package org.mockito.internal;
-import org.mockito.Incubating;
-import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockingDetails;
import org.mockito.Mockito;
@@ -29,21 +27,6 @@
import static org.mockito.internal.util.StringUtil.*;
import static org.mockito.internal.verification.VerificationModeFactory.*;
-/**
- * Represents an active mock of a type's static methods. The mocking only affects the thread
- * on which this static mock was created and it is not safe to use this object from another
- * thread. The static mock is released when this object's {@link MockedStaticImpl#close()} method
- * is invoked. If this object is never closed, the static mock will remain active on the
- * initiating thread. It is therefore recommended to create this object within a try-with-resources
- * statement unless when managed explicitly, for example by using a JUnit rule or extension.
- *
- * If the {@link Mock} annotation is used on fields or method parameters of this type, a static mock
- * is created instead of a regular mock. The static mock is activated and released upon completing any
- * relevant test.
- *
- * @param The type being mocked.
- */
-@Incubating
public final class MockedStaticImpl implements MockedStatic {
private final MockMaker.StaticMockControl control;
diff --git a/src/main/java/org/mockito/internal/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java
index bccb045fe1..6901baef85 100644
--- a/src/main/java/org/mockito/internal/MockitoCore.java
+++ b/src/main/java/org/mockito/internal/MockitoCore.java
@@ -4,25 +4,7 @@
*/
package org.mockito.internal;
-import static org.mockito.internal.exceptions.Reporter.*;
-import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
-import static org.mockito.internal.util.MockUtil.createMock;
-import static org.mockito.internal.util.MockUtil.createStaticMock;
-import static org.mockito.internal.util.MockUtil.getInvocationContainer;
-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;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.mockito.InOrder;
-import org.mockito.MockSettings;
-import org.mockito.MockedStatic;
-import org.mockito.MockingDetails;
+import org.mockito.*;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.internal.creation.MockSettingsImpl;
import org.mockito.internal.invocation.finder.VerifiableInvocationsFinder;
@@ -49,6 +31,16 @@
import org.mockito.stubbing.Stubber;
import org.mockito.verification.VerificationMode;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import static org.mockito.internal.exceptions.Reporter.*;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
+import static org.mockito.internal.util.MockUtil.*;
+import static org.mockito.internal.verification.VerificationModeFactory.noInteractions;
+import static org.mockito.internal.verification.VerificationModeFactory.noMoreInteractions;
+
@SuppressWarnings("unchecked")
public class MockitoCore {
@@ -87,6 +79,29 @@ public MockedStatic mockStatic(Class classToMock, MockSettings setting
return new MockedStaticImpl<>(control);
}
+ public MockedConstruction mockConstruction(
+ Class typeToMock,
+ Function settingsFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ Function> creationSettings =
+ context -> {
+ MockSettings value = settingsFactory.apply(context);
+ if (!MockSettingsImpl.class.isInstance(value)) {
+ throw new IllegalArgumentException(
+ "Unexpected implementation of '"
+ + value.getClass().getCanonicalName()
+ + "'\n"
+ + "At the moment, you cannot provide your own implementations of that class.");
+ }
+ MockSettingsImpl impl = MockSettingsImpl.class.cast(value);
+ return impl.build(typeToMock);
+ };
+ MockMaker.ConstructionMockControl control =
+ createConstructionMock(typeToMock, creationSettings, mockInitializer);
+ control.enable();
+ return new MockedConstructionImpl<>(control);
+ }
+
public OngoingStubbing when(T methodCall) {
MockingProgress mockingProgress = mockingProgress();
mockingProgress.stubbingStarted();
diff --git a/src/main/java/org/mockito/internal/configuration/ClassPathLoader.java b/src/main/java/org/mockito/internal/configuration/ClassPathLoader.java
index 8fc47b425f..0f031ee58a 100644
--- a/src/main/java/org/mockito/internal/configuration/ClassPathLoader.java
+++ b/src/main/java/org/mockito/internal/configuration/ClassPathLoader.java
@@ -64,7 +64,7 @@ public IMockitoConfiguration loadConfiguration() {
}
try {
- return (IMockitoConfiguration) configClass.newInstance();
+ return (IMockitoConfiguration) configClass.getDeclaredConstructor().newInstance();
} catch (ClassCastException e) {
throw new MockitoConfigurationException(
"MockitoConfiguration class must implement "
diff --git a/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java
index 24cb7a1cb2..43a35eee6e 100644
--- a/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java
+++ b/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java
@@ -5,7 +5,6 @@
package org.mockito.internal.configuration;
import static org.mockito.internal.exceptions.Reporter.moreThanOneAnnotationNotAllowed;
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
@@ -16,10 +15,12 @@
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
+import org.mockito.ScopedMock;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.plugins.AnnotationEngine;
+import org.mockito.plugins.MemberAccessor;
/**
* Initializes fields annotated with @{@link org.mockito.Mock} or @{@link org.mockito.Captor}.
@@ -64,23 +65,24 @@ private void registerAnnotationProcessor(
@Override
public AutoCloseable process(Class> clazz, Object testInstance) {
- List> mockedStatics = new ArrayList<>();
+ List scopedMocks = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
boolean alreadyAssigned = false;
for (Annotation annotation : field.getAnnotations()) {
Object mock = createMockFor(annotation, field);
- if (mock instanceof MockedStatic>) {
- mockedStatics.add((MockedStatic>) mock);
+ if (mock instanceof ScopedMock) {
+ scopedMocks.add((ScopedMock) mock);
}
if (mock != null) {
throwIfAlreadyAssigned(field, alreadyAssigned);
alreadyAssigned = true;
+ final MemberAccessor accessor = Plugins.getMemberAccessor();
try {
- setField(testInstance, field, mock);
+ accessor.set(field, testInstance, mock);
} catch (Exception e) {
- for (MockedStatic> mockedStatic : mockedStatics) {
- mockedStatic.close();
+ for (ScopedMock scopedMock : scopedMocks) {
+ scopedMock.close();
}
throw new MockitoException(
"Problems setting field "
@@ -93,8 +95,8 @@ public AutoCloseable process(Class> clazz, Object testInstance) {
}
}
return () -> {
- for (MockedStatic> mockedStatic : mockedStatics) {
- mockedStatic.closeOnDemand();
+ for (ScopedMock scopedMock : scopedMocks) {
+ scopedMock.closeOnDemand();
}
};
}
diff --git a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java
index 59239a4674..176d3e5006 100644
--- a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java
+++ b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java
@@ -12,8 +12,8 @@
import java.util.List;
import java.util.Set;
-import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
+import org.mockito.ScopedMock;
import org.mockito.internal.configuration.injection.scanner.InjectMocksScanner;
import org.mockito.internal.configuration.injection.scanner.MockScanner;
import org.mockito.plugins.AnnotationEngine;
@@ -119,8 +119,8 @@ private AutoCloseable injectCloseableMocks(final Object testClassInstance) {
return () -> {
for (Object mock : mocks) {
- if (mock instanceof MockedStatic>) {
- ((MockedStatic>) mock).closeOnDemand();
+ if (mock instanceof ScopedMock) {
+ ((ScopedMock) mock).closeOnDemand();
}
}
};
diff --git a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
index 48f0515a4b..e48291e5d5 100644
--- a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
+++ b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java
@@ -12,6 +12,7 @@
import org.mockito.Mock;
import org.mockito.MockSettings;
+import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoException;
@@ -52,13 +53,21 @@ public static Object processAnnotationForMock(
mockSettings.defaultAnswer(annotation.answer());
if (type == MockedStatic.class) {
- return Mockito.mockStatic(inferStaticMock(genericType.get(), name), mockSettings);
+ return Mockito.mockStatic(
+ inferParameterizedType(
+ genericType.get(), name, MockedStatic.class.getSimpleName()),
+ mockSettings);
+ } else if (type == MockedConstruction.class) {
+ return Mockito.mockConstruction(
+ inferParameterizedType(
+ genericType.get(), name, MockedConstruction.class.getSimpleName()),
+ mockSettings);
} else {
return Mockito.mock(type, mockSettings);
}
}
- static Class> inferStaticMock(Type type, String name) {
+ static Class> inferParameterizedType(Type type, String name, String sort) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] arguments = parameterizedType.getActualTypeArguments();
@@ -72,11 +81,11 @@ static Class> inferStaticMock(Type type, String name) {
join(
"Mockito cannot infer a static mock from a raw type for " + name,
"",
- "Instead of @Mock MockedStatic you need to specify a parameterized type",
- "For example, if you would like to mock static methods of Sample.class, specify",
+ "Instead of @Mock " + sort + " you need to specify a parameterized type",
+ "For example, if you would like to mock Sample.class, specify",
"",
- "@Mock MockedStatic",
+ "@Mock " + sort + "",
"",
- "as the type parameter. If the type is parameterized, it should be specified as raw type."));
+ "as the type parameter. If the type is itself parameterized, it should be specified as raw type."));
}
}
diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
index b4686b30d8..b7a4a2cef9 100644
--- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
+++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java
@@ -22,8 +22,10 @@
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import org.mockito.plugins.AnnotationEngine;
+import org.mockito.plugins.MemberAccessor;
/**
* Process fields annotated with @Spy.
@@ -50,23 +52,23 @@ public class SpyAnnotationEngine
@Override
public AutoCloseable process(Class> context, Object testInstance) {
Field[] fields = context.getDeclaredFields();
+ MemberAccessor accessor = Plugins.getMemberAccessor();
for (Field field : fields) {
if (field.isAnnotationPresent(Spy.class)
&& !field.isAnnotationPresent(InjectMocks.class)) {
assertNoIncompatibleAnnotations(Spy.class, field, Mock.class, Captor.class);
- field.setAccessible(true);
Object instance;
try {
- instance = field.get(testInstance);
+ instance = accessor.get(field, testInstance);
if (MockUtil.isMock(instance)) {
// instance has been spied earlier
// for example happens when MockitoAnnotations.openMocks is called two
// times.
Mockito.reset(instance);
} else if (instance != null) {
- field.set(testInstance, spyInstance(field, instance));
+ accessor.set(field, testInstance, spyInstance(field, instance));
} else {
- field.set(testInstance, spyNewInstance(testInstance, field));
+ accessor.set(field, testInstance, spyNewInstance(testInstance, field));
}
} catch (Exception e) {
throw new MockitoException(
@@ -123,8 +125,8 @@ private static Object spyNewInstance(Object testInstance, Field field)
Constructor> constructor = noArgConstructorOf(type);
if (Modifier.isPrivate(constructor.getModifiers())) {
- constructor.setAccessible(true);
- return Mockito.mock(type, settings.spiedInstance(constructor.newInstance()));
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ return Mockito.mock(type, settings.spiedInstance(accessor.newInstance(constructor)));
} else {
return Mockito.mock(type, settings.useConstructor());
}
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 cd8fcc5e3a..6230e1b86f 100644
--- a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java
+++ b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java
@@ -5,7 +5,6 @@
package org.mockito.internal.configuration.injection;
import static org.mockito.Mockito.withSettings;
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
import java.lang.reflect.Field;
import java.util.Set;
@@ -13,8 +12,10 @@
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.util.reflection.FieldReader;
+import org.mockito.plugins.MemberAccessor;
/**
* Handler for field annotated with @InjectMocks and @Spy.
@@ -26,6 +27,8 @@
*/
public class SpyOnInjectedFieldsHandler extends MockInjectionStrategy {
+ private final MemberAccessor accessor = Plugins.getMemberAccessor();
+
@Override
protected boolean processInjection(Field field, Object fieldOwner, Set mockCandidates) {
FieldReader fieldReader = new FieldReader(fieldOwner, field);
@@ -46,7 +49,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set m
.spiedInstance(instance)
.defaultAnswer(Mockito.CALLS_REAL_METHODS)
.name(field.getName()));
- setField(fieldOwner, field, mock);
+ accessor.set(field, fieldOwner, mock);
}
} catch (Exception e) {
throw new MockitoException("Problems initiating spied field " + field.getName(), e);
diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java
index a682d92320..6c3c329b67 100644
--- a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java
+++ b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java
@@ -5,13 +5,14 @@
package org.mockito.internal.configuration.injection.filter;
import static org.mockito.internal.exceptions.Reporter.cannotInjectDependency;
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.reflection.BeanPropertySetter;
+import org.mockito.plugins.MemberAccessor;
/**
* This node returns an actual injecter which will be either :
@@ -30,18 +31,17 @@ public OngoingInjector filterCandidate(
if (mocks.size() == 1) {
final Object matchingMock = mocks.iterator().next();
- return new OngoingInjector() {
- public Object thenInject() {
- try {
- if (!new BeanPropertySetter(injectee, candidateFieldToBeInjected)
- .set(matchingMock)) {
- setField(injectee, candidateFieldToBeInjected, matchingMock);
- }
- } catch (RuntimeException e) {
- throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e);
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ return () -> {
+ try {
+ if (!new BeanPropertySetter(injectee, candidateFieldToBeInjected)
+ .set(matchingMock)) {
+ accessor.set(candidateFieldToBeInjected, injectee, matchingMock);
}
- return matchingMock;
+ } catch (RuntimeException | IllegalAccessException e) {
+ throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e);
}
+ return matchingMock;
};
}
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 46bf3a7c0e..38b882a322 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
@@ -8,19 +8,13 @@
import java.util.Map;
import org.mockito.internal.creation.instance.InstantiatorProvider2Adapter;
-import org.mockito.plugins.AnnotationEngine;
-import org.mockito.plugins.InstantiatorProvider;
-import org.mockito.plugins.InstantiatorProvider2;
-import org.mockito.plugins.MockMaker;
-import org.mockito.plugins.MockitoLogger;
-import org.mockito.plugins.MockitoPlugins;
-import org.mockito.plugins.PluginSwitch;
-import org.mockito.plugins.StackTraceCleanerProvider;
+import org.mockito.plugins.*;
class DefaultMockitoPlugins implements MockitoPlugins {
private static final Map DEFAULT_PLUGINS = new HashMap();
static final String INLINE_ALIAS = "mock-maker-inline";
+ static final String MODULE_ALIAS = "member-accessor-module";
static {
// Keep the mapping: plugin interface name -> plugin implementation class name
@@ -41,6 +35,11 @@ class DefaultMockitoPlugins implements MockitoPlugins {
INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker");
DEFAULT_PLUGINS.put(
MockitoLogger.class.getName(), "org.mockito.internal.util.ConsoleMockitoLogger");
+ DEFAULT_PLUGINS.put(
+ MemberAccessor.class.getName(),
+ "org.mockito.internal.util.reflection.ReflectionMemberAccessor");
+ DEFAULT_PLUGINS.put(
+ MODULE_ALIAS, "org.mockito.internal.util.reflection.ModuleMemberAccessor");
}
@Override
@@ -80,7 +79,7 @@ private T create(Class pluginType, String className) {
// Default implementation. Use our own ClassLoader instead of the context
// ClassLoader, as the default implementation is assumed to be part of
// Mockito and may not be available via the context ClassLoader.
- return pluginType.cast(Class.forName(className).newInstance());
+ return pluginType.cast(Class.forName(className).getDeclaredConstructor().newInstance());
} catch (Exception e) {
throw new IllegalStateException(
"Internal problem occurred, please report it. "
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 d58ca2cdf3..8f8f76edcb 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginInitializer.java
@@ -47,7 +47,7 @@ public T loadImpl(Class service) {
classOrAlias = plugins.getDefaultPluginClass(alias);
}
Class> pluginClass = loader.loadClass(classOrAlias);
- Object plugin = pluginClass.newInstance();
+ Object plugin = pluginClass.getDeclaredConstructor().newInstance();
return service.cast(plugin);
}
return null;
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 0419001285..ba7aae1728 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/PluginRegistry.java
@@ -5,13 +5,7 @@
package org.mockito.internal.configuration.plugins;
import org.mockito.internal.creation.instance.InstantiatorProviderAdapter;
-import org.mockito.plugins.AnnotationEngine;
-import org.mockito.plugins.InstantiatorProvider;
-import org.mockito.plugins.InstantiatorProvider2;
-import org.mockito.plugins.MockMaker;
-import org.mockito.plugins.MockitoLogger;
-import org.mockito.plugins.PluginSwitch;
-import org.mockito.plugins.StackTraceCleanerProvider;
+import org.mockito.plugins.*;
class PluginRegistry {
@@ -22,6 +16,10 @@ class PluginRegistry {
new PluginLoader(pluginSwitch, DefaultMockitoPlugins.INLINE_ALIAS)
.loadPlugin(MockMaker.class);
+ private final MemberAccessor memberAccessor =
+ new PluginLoader(pluginSwitch, DefaultMockitoPlugins.MODULE_ALIAS)
+ .loadPlugin(MemberAccessor.class);
+
private final StackTraceCleanerProvider stackTraceCleanerProvider =
new PluginLoader(pluginSwitch).loadPlugin(StackTraceCleanerProvider.class);
@@ -62,6 +60,16 @@ MockMaker getMockMaker() {
return mockMaker;
}
+ /**
+ * Returns the implementation of the member accessor available for the current runtime.
+ *
+ * Returns {@link org.mockito.internal.util.reflection.ReflectionMemberAccessor} if no
+ * {@link org.mockito.plugins.MockMaker} extension exists or is visible in the current classpath.
+ */
+ MemberAccessor getMemberAccessor() {
+ return memberAccessor;
+ }
+
/**
* Returns the instantiator provider available for the current runtime.
*
diff --git a/src/main/java/org/mockito/internal/configuration/plugins/Plugins.java b/src/main/java/org/mockito/internal/configuration/plugins/Plugins.java
index 8469981202..603a03008a 100644
--- a/src/main/java/org/mockito/internal/configuration/plugins/Plugins.java
+++ b/src/main/java/org/mockito/internal/configuration/plugins/Plugins.java
@@ -4,12 +4,7 @@
*/
package org.mockito.internal.configuration.plugins;
-import org.mockito.plugins.AnnotationEngine;
-import org.mockito.plugins.InstantiatorProvider2;
-import org.mockito.plugins.MockMaker;
-import org.mockito.plugins.MockitoLogger;
-import org.mockito.plugins.MockitoPlugins;
-import org.mockito.plugins.StackTraceCleanerProvider;
+import org.mockito.plugins.*;
/**
* Access to Mockito behavior that can be reconfigured by plugins
@@ -35,6 +30,16 @@ public static MockMaker getMockMaker() {
return registry.getMockMaker();
}
+ /**
+ * Returns the implementation of the member accessor available for the current runtime.
+ *
+ * Returns default member accessor if no
+ * {@link org.mockito.plugins.MemberAccessor} extension exists or is visible in the current classpath.
+ */
+ public static MemberAccessor getMemberAccessor() {
+ return registry.getMemberAccessor();
+ }
+
/**
* Returns the instantiator provider available for the current runtime.
*
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyCrossClassLoaderSerializationSupport.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyCrossClassLoaderSerializationSupport.java
index 4bb4405e73..90ccee882c 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyCrossClassLoaderSerializationSupport.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyCrossClassLoaderSerializationSupport.java
@@ -6,7 +6,6 @@
import static org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForWriteReplace;
import static org.mockito.internal.util.StringUtil.join;
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
import java.io.*;
import java.lang.reflect.Field;
@@ -22,6 +21,7 @@
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
+import org.mockito.plugins.MemberAccessor;
/**
* This is responsible for serializing a mock, it is enabled if the mock is implementing {@link Serializable}.
@@ -318,8 +318,14 @@ protected Class> resolveClass(ObjectStreamClass desc)
private void hackClassNameToMatchNewlyCreatedClass(
ObjectStreamClass descInstance, Class> proxyClass) throws ObjectStreamException {
try {
+ MemberAccessor accessor = Plugins.getMemberAccessor();
Field classNameField = descInstance.getClass().getDeclaredField("name");
- setField(descInstance, classNameField, proxyClass.getCanonicalName());
+ try {
+ accessor.set(classNameField, descInstance, proxyClass.getCanonicalName());
+ } catch (IllegalAccessException e) {
+ throw new MockitoSerializationIssue(
+ "Access to " + classNameField + " was denied", e);
+ }
} catch (NoSuchFieldException nsfe) {
throw new MockitoSerializationIssue(
join(
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
index 8ea513e2c6..5263ea1262 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
@@ -5,9 +5,13 @@
package org.mockito.internal.creation.bytebuddy;
import org.mockito.Incubating;
+import org.mockito.MockedConstruction;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
+import java.util.Optional;
+import java.util.function.Function;
+
/**
* ByteBuddy MockMaker.
*
@@ -25,6 +29,12 @@ public T createMock(MockCreationSettings settings, MockHandler handler) {
return defaultByteBuddyMockMaker.createMock(settings, handler);
}
+ @Override
+ public Optional createSpy(
+ MockCreationSettings settings, MockHandler handler, T object) {
+ return defaultByteBuddyMockMaker.createSpy(settings, handler, object);
+ }
+
@Override
public Class extends T> createMockType(MockCreationSettings creationSettings) {
return defaultByteBuddyMockMaker.createMockType(creationSettings);
@@ -51,4 +61,14 @@ public StaticMockControl createStaticMock(
Class type, MockCreationSettings settings, MockHandler handler) {
return defaultByteBuddyMockMaker.createStaticMock(type, settings, handler);
}
+
+ @Override
+ public ConstructionMockControl createConstructionMock(
+ Class type,
+ Function> settingsFactory,
+ Function> handlerFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ return defaultByteBuddyMockMaker.createConstructionMock(
+ type, settingsFactory, handlerFactory, mockInitializer);
+ }
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java
index b87d7cb6b7..b27b9727a1 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java
@@ -8,5 +8,7 @@ public interface BytecodeGenerator {
Class extends T> mockClass(MockFeatures features);
+ void mockClassConstruction(Class> type);
+
void mockClassStatic(Class> type);
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ConstructionCallback.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ConstructionCallback.java
new file mode 100644
index 0000000000..ac8ecc4040
--- /dev/null
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ConstructionCallback.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.creation.bytebuddy;
+
+public interface ConstructionCallback {
+
+ Object apply(Class> type, Object object, Object[] arguments, String[] parameterTypeNames);
+}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
index 097b8c8aae..0a20882b2c 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java
@@ -9,20 +9,25 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.WeakHashMap;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.mockito.Incubating;
+import org.mockito.MockedConstruction;
+import org.mockito.creation.instance.InstantiationException;
import org.mockito.creation.instance.Instantiator;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.base.MockitoInitializationException;
+import org.mockito.exceptions.misusing.MockitoConfigurationException;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Platform;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
@@ -30,6 +35,7 @@
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
import org.mockito.plugins.InlineMockMaker;
+import org.mockito.plugins.MemberAccessor;
import static org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.*;
import static org.mockito.internal.util.StringUtil.*;
@@ -93,7 +99,8 @@
* support this feature.
*/
@Incubating
-public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker, InlineMockMaker {
+public class InlineByteBuddyMockMaker
+ implements ClassCreatingMockMaker, InlineMockMaker, Instantiator {
private static final Instrumentation INSTRUMENTATION;
@@ -188,6 +195,13 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker, InlineM
private final DetachedThreadLocal, MockMethodInterceptor>> mockedStatics =
new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.INLINE);
+ private final DetachedThreadLocal, BiConsumer>>
+ mockedConstruction = new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.INLINE);
+
+ private final ThreadLocal mockitoConstruction = ThreadLocal.withInitial(() -> false);
+
+ private final ThreadLocal currentSpied = new ThreadLocal<>();
+
public InlineByteBuddyMockMaker() {
if (INITIALIZATION_ERROR != null) {
String detail;
@@ -221,18 +235,101 @@ public InlineByteBuddyMockMaker() {
Platform.describe()),
INITIALIZATION_ERROR);
}
+
+ ThreadLocal> currentConstruction = new ThreadLocal<>();
+ ThreadLocal isSuspended = ThreadLocal.withInitial(() -> false);
+ Predicate> isMockConstruction =
+ type -> {
+ if (isSuspended.get()) {
+ return false;
+ } else if (mockitoConstruction.get() || currentConstruction.get() != null) {
+ return true;
+ }
+ Map, ?> interceptors = mockedConstruction.get();
+ if (interceptors != null && interceptors.containsKey(type)) {
+ currentConstruction.set(type);
+ return true;
+ } else {
+ return false;
+ }
+ };
+ ConstructionCallback onConstruction =
+ (type, object, arguments, parameterTypeNames) -> {
+ if (mockitoConstruction.get()) {
+ return currentSpied.get();
+ } else if (currentConstruction.get() != type) {
+ return null;
+ }
+ currentConstruction.remove();
+ isSuspended.set(true);
+ try {
+ Map, BiConsumer> interceptors =
+ mockedConstruction.get();
+ if (interceptors != null) {
+ BiConsumer interceptor =
+ interceptors.get(type);
+ if (interceptor != null) {
+ interceptor.accept(
+ object,
+ new InlineConstructionMockContext(
+ arguments, object.getClass(), parameterTypeNames));
+ }
+ }
+ } finally {
+ isSuspended.set(false);
+ }
+ return null;
+ };
+
bytecodeGenerator =
new TypeCachingBytecodeGenerator(
- new InlineBytecodeGenerator(INSTRUMENTATION, mocks, mockedStatics), true);
+ new InlineBytecodeGenerator(
+ INSTRUMENTATION,
+ mocks,
+ mockedStatics,
+ isMockConstruction,
+ onConstruction),
+ true);
}
@Override
public T createMock(MockCreationSettings settings, MockHandler handler) {
+ return doCreateMock(settings, handler, false);
+ }
+
+ @Override
+ public Optional createSpy(
+ MockCreationSettings settings, MockHandler handler, T object) {
+ if (object == null) {
+ throw new MockitoConfigurationException("Spy instance must not be null");
+ }
+ currentSpied.set(object);
+ try {
+ return Optional.ofNullable(doCreateMock(settings, handler, true));
+ } finally {
+ currentSpied.remove();
+ }
+ }
+
+ private T doCreateMock(
+ MockCreationSettings settings,
+ MockHandler handler,
+ boolean nullOnNonInlineConstruction) {
Class extends T> type = createMockType(settings);
- Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
try {
- T instance = instantiator.newInstance(type);
+ T instance;
+ try {
+ // We attempt to use the "native" mock maker first that avoids Objenesis and Unsafe
+ instance = newInstance(type);
+ } catch (InstantiationException ignored) {
+ if (nullOnNonInlineConstruction) {
+ return null;
+ }
+ Instantiator instantiator =
+ Plugins.getInstantiatorProvider().getInstantiator(settings);
+ instance = instantiator.newInstance(type);
+ }
MockMethodInterceptor mockMethodInterceptor =
new MockMethodInterceptor(handler, settings);
mocks.put(instance, mockMethodInterceptor);
@@ -424,6 +521,90 @@ public StaticMockControl createStaticMock(
return new InlineStaticMockControl<>(type, interceptors, settings, handler);
}
+ @Override
+ public ConstructionMockControl createConstructionMock(
+ Class type,
+ Function> settingsFactory,
+ Function> handlerFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ if (type == Object.class) {
+ throw new MockitoException(
+ "It is not possible to mock construction of the Object class "
+ + "to avoid inference with default object constructor chains");
+ } else if (type.isPrimitive() || Modifier.isAbstract(type.getModifiers())) {
+ throw new MockitoException(
+ "It is not possible to construct primitive types or abstract types: "
+ + type.getTypeName());
+ }
+
+ bytecodeGenerator.mockClassConstruction(type);
+
+ Map, BiConsumer> interceptors =
+ mockedConstruction.get();
+ if (interceptors == null) {
+ interceptors = new WeakHashMap<>();
+ mockedConstruction.set(interceptors);
+ }
+
+ return new InlineConstructionMockControl<>(
+ type, settingsFactory, handlerFactory, mockInitializer, interceptors);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T newInstance(Class cls) throws InstantiationException {
+ Constructor>[] constructors = cls.getDeclaredConstructors();
+ if (constructors.length == 0) {
+ throw new InstantiationException(cls.getTypeName() + " does not define a constructor");
+ }
+ Constructor> selected = constructors[0];
+ for (Constructor> constructor : constructors) {
+ if (Modifier.isPublic(constructor.getModifiers())) {
+ selected = constructor;
+ break;
+ }
+ }
+ Class>[] types = selected.getParameterTypes();
+ Object[] arguments = new Object[types.length];
+ int index = 0;
+ for (Class> type : types) {
+ arguments[index++] = makeStandardArgument(type);
+ }
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ try {
+ mockitoConstruction.set(true);
+ try {
+ return (T) accessor.newInstance(selected, arguments);
+ } finally {
+ mockitoConstruction.set(false);
+ }
+ } catch (Exception e) {
+ throw new InstantiationException("Could not instantiate " + cls.getTypeName(), e);
+ }
+ }
+
+ private Object makeStandardArgument(Class> type) {
+ if (type == boolean.class) {
+ return false;
+ } else if (type == byte.class) {
+ return (byte) 0;
+ } else if (type == short.class) {
+ return (short) 0;
+ } else if (type == char.class) {
+ return (char) 0;
+ } else if (type == int.class) {
+ return 0;
+ } else if (type == long.class) {
+ return 0L;
+ } else if (type == float.class) {
+ return 0f;
+ } else if (type == double.class) {
+ return 0d;
+ } else {
+ return null;
+ }
+ }
+
private static class InlineStaticMockControl implements StaticMockControl {
private final Class type;
@@ -431,6 +612,7 @@ private static class InlineStaticMockControl implements StaticMockControl
private final Map, MockMethodInterceptor> interceptors;
private final MockCreationSettings settings;
+
private final MockHandler handler;
private InlineStaticMockControl(
@@ -478,4 +660,167 @@ public void disable() {
}
}
}
+
+ private class InlineConstructionMockControl implements ConstructionMockControl {
+
+ private final Class type;
+
+ private final Function> settingsFactory;
+ private final Function> handlerFactory;
+
+ private final MockedConstruction.MockInitializer mockInitializer;
+
+ private final Map, BiConsumer> interceptors;
+
+ private final List all = new ArrayList<>();
+ private int count;
+
+ private InlineConstructionMockControl(
+ Class type,
+ Function> settingsFactory,
+ Function> handlerFactory,
+ MockedConstruction.MockInitializer mockInitializer,
+ Map, BiConsumer> interceptors) {
+ this.type = type;
+ this.settingsFactory = settingsFactory;
+ this.handlerFactory = handlerFactory;
+ this.mockInitializer = mockInitializer;
+ this.interceptors = interceptors;
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ @Override
+ public void enable() {
+ if (interceptors.putIfAbsent(
+ type,
+ (object, context) -> {
+ ((InlineConstructionMockContext) context).count = ++count;
+ MockMethodInterceptor interceptor =
+ new MockMethodInterceptor(
+ handlerFactory.apply(context),
+ settingsFactory.apply(context));
+ mocks.put(object, interceptor);
+ try {
+ @SuppressWarnings("unchecked")
+ T cast = (T) object;
+ mockInitializer.prepare(cast, context);
+ } catch (Throwable t) {
+ mocks.remove(object); // TODO: filter stack trace?
+ throw new MockitoException(
+ "Could not initialize mocked construction", t);
+ }
+ all.add(object);
+ })
+ != null) {
+ throw new MockitoException(
+ join(
+ "For "
+ + type.getName()
+ + ", static mocking is already registered in the current thread",
+ "",
+ "To create a new mock, the existing static mock registration must be deregistered"));
+ }
+ }
+
+ @Override
+ public void disable() {
+ if (interceptors.remove(type) == null) {
+ throw new MockitoException(
+ join(
+ "Could not deregister "
+ + type.getName()
+ + " as a static mock since it is not currently registered",
+ "",
+ "To register a static mock, use Mockito.mockStatic("
+ + type.getSimpleName()
+ + ".class)"));
+ }
+ all.clear();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List getMocks() {
+ return (List) all;
+ }
+ }
+
+ private static class InlineConstructionMockContext implements MockedConstruction.Context {
+
+ private static final Map> PRIMITIVES = new HashMap<>();
+
+ static {
+ PRIMITIVES.put(boolean.class.getTypeName(), boolean.class);
+ PRIMITIVES.put(byte.class.getTypeName(), byte.class);
+ PRIMITIVES.put(short.class.getTypeName(), short.class);
+ PRIMITIVES.put(char.class.getTypeName(), char.class);
+ PRIMITIVES.put(int.class.getTypeName(), int.class);
+ PRIMITIVES.put(long.class.getTypeName(), long.class);
+ PRIMITIVES.put(float.class.getTypeName(), float.class);
+ PRIMITIVES.put(double.class.getTypeName(), double.class);
+ }
+
+ private int count;
+
+ private final Object[] arguments;
+ private final Class> type;
+ private final String[] parameterTypeNames;
+
+ private InlineConstructionMockContext(
+ Object[] arguments, Class> type, String[] parameterTypeNames) {
+ this.arguments = arguments;
+ this.type = type;
+ this.parameterTypeNames = parameterTypeNames;
+ }
+
+ @Override
+ public int getCount() {
+ if (count == 0) {
+ throw new MockitoConfigurationException(
+ "mocked construction context is not initialized");
+ }
+ return count;
+ }
+
+ @Override
+ public Constructor> constructor() {
+ Class>[] parameterTypes = new Class>[parameterTypeNames.length];
+ int index = 0;
+ for (String parameterTypeName : parameterTypeNames) {
+ if (PRIMITIVES.containsKey(parameterTypeName)) {
+ parameterTypes[index++] = PRIMITIVES.get(parameterTypeName);
+ } else {
+ try {
+ parameterTypes[index++] =
+ Class.forName(parameterTypeName, false, type.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new MockitoException(
+ "Could not find parameter of type " + parameterTypeName, e);
+ }
+ }
+ }
+ try {
+ return type.getDeclaredConstructor(parameterTypes);
+ } catch (NoSuchMethodException e) {
+ throw new MockitoException(
+ join(
+ "Could not resolve constructor of type",
+ "",
+ type.getTypeName(),
+ "",
+ "with arguments of types",
+ Arrays.toString(parameterTypes)),
+ e);
+ }
+ }
+
+ @Override
+ public List> arguments() {
+ return Collections.unmodifiableList(Arrays.asList(arguments));
+ }
+ }
}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
index 187f1ea945..fc3c69df13 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
@@ -4,17 +4,17 @@
*/
package org.mockito.internal.creation.bytebuddy;
-import static net.bytebuddy.implementation.MethodDelegation.withDefaultConfiguration;
-import static net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of;
-import static net.bytebuddy.matcher.ElementMatchers.*;
-import static org.mockito.internal.util.StringUtil.join;
-
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
@@ -43,6 +43,11 @@
import org.mockito.internal.util.concurrent.WeakConcurrentSet;
import org.mockito.mock.SerializableMode;
+import static net.bytebuddy.implementation.MethodDelegation.*;
+import static net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.*;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.mockito.internal.util.StringUtil.*;
+
public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTransformer {
private static final String PRELOAD = "org.mockito.inline.preload";
@@ -75,7 +80,9 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran
public InlineBytecodeGenerator(
Instrumentation instrumentation,
WeakConcurrentMap mocks,
- DetachedThreadLocal, MockMethodInterceptor>> mockedStatics) {
+ DetachedThreadLocal, MockMethodInterceptor>> mockedStatics,
+ Predicate> isMockConstruction,
+ ConstructionCallback onConstruction) {
preload();
this.instrumentation = instrumentation;
byteBuddy =
@@ -118,6 +125,7 @@ public InlineBytecodeGenerator(
Advice.withCustomMapping()
.bind(MockMethodAdvice.Identifier.class, identifier)
.to(MockMethodAdvice.ForStatic.class))
+ .constructor(any(), new MockMethodAdvice.ConstructorShortcut(identifier))
.method(
isHashCode(),
Advice.withCustomMapping()
@@ -150,7 +158,9 @@ public InlineBytecodeGenerator(
this.canRead = canRead;
this.redefineModule = redefineModule;
MockMethodDispatcher.set(
- identifier, new MockMethodAdvice(mocks, mockedStatics, identifier));
+ identifier,
+ new MockMethodAdvice(
+ mocks, mockedStatics, identifier, isMockConstruction, onConstruction));
instrumentation.addTransformer(this, true);
}
@@ -202,10 +212,15 @@ public Class extends T> mockClass(MockFeatures features) {
}
@Override
- public void mockClassStatic(Class> type) {
+ public synchronized void mockClassStatic(Class> type) {
triggerRetransformation(Collections.singleton(type), true);
}
+ @Override
+ public synchronized void mockClassConstruction(Class> type) {
+ triggerRetransformation(Collections.singleton(type), false);
+ }
+
private void triggerRetransformation(Set> types, boolean flat) {
Set> targets = new HashSet>();
@@ -335,8 +350,12 @@ public byte[] transform(
return byteBuddy
.redefine(
classBeingRedefined,
+ // new ClassFileLocator.Compound(
ClassFileLocator.Simple.of(
- classBeingRedefined.getName(), classfileBuffer))
+ classBeingRedefined.getName(), classfileBuffer)
+ // ,ClassFileLocator.ForClassLoader.ofSystemLoader()
+ // )
+ )
// Note: The VM erases parameter meta data from the provided class file
// (bug). We just add this information manually.
.visit(new ParameterWritingVisitorWrapper(classBeingRedefined))
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
index 072a958577..69a7ed21a6 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
@@ -12,18 +12,34 @@
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.function.Predicate;
+import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.This;
+import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.jar.asm.Label;
+import net.bytebuddy.jar.asm.MethodVisitor;
+import net.bytebuddy.jar.asm.Opcodes;
+import net.bytebuddy.jar.asm.Type;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.OpenedClassReader;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher;
import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
@@ -33,6 +49,9 @@
import org.mockito.internal.invocation.mockref.MockWeakReference;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
import org.mockito.internal.util.concurrent.WeakConcurrentMap;
+import org.mockito.plugins.MemberAccessor;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
public class MockMethodAdvice extends MockMethodDispatcher {
@@ -46,13 +65,20 @@ public class MockMethodAdvice extends MockMethodDispatcher {
private final WeakConcurrentMap, SoftReference> graphs =
new WeakConcurrentMap.WithInlinedExpunction, SoftReference>();
+ private final Predicate> isMockConstruction;
+ private final ConstructionCallback onConstruction;
+
public MockMethodAdvice(
WeakConcurrentMap interceptors,
DetachedThreadLocal, MockMethodInterceptor>> mockedStatics,
- String identifier) {
+ String identifier,
+ Predicate> isMockConstruction,
+ ConstructionCallback onConstruction) {
this.interceptors = interceptors;
this.mockedStatics = mockedStatics;
+ this.onConstruction = onConstruction;
this.identifier = identifier;
+ this.isMockConstruction = isMockConstruction;
}
@SuppressWarnings("unused")
@@ -144,6 +170,12 @@ public Callable> handleStatic(Class> type, Method origin, Object[] arguments
new LocationImpl(new Throwable(), true)));
}
+ @Override
+ public Object handleConstruction(
+ Class> type, Object object, Object[] arguments, String[] parameterTypeNames) {
+ return onConstruction.apply(type, object, arguments, parameterTypeNames);
+ }
+
@Override
public boolean isMock(Object instance) {
// We need to exclude 'interceptors.target' explicitly to avoid a recursive check on whether
@@ -183,6 +215,11 @@ public boolean isOverridden(Object instance, Method origin) {
.represents(origin.getDeclaringClass());
}
+ @Override
+ public boolean isConstructorMock(Class> type) {
+ return isMockConstruction.test(type);
+ }
+
private static class RealMethodCall implements RealMethod {
private final SelfCallInfo selfCallInfo;
@@ -208,10 +245,6 @@ public boolean isInvokable() {
@Override
public Object invoke() throws Throwable {
- if (!Modifier.isPublic(
- origin.getDeclaringClass().getModifiers() & origin.getModifiers())) {
- origin.setAccessible(true);
- }
selfCallInfo.set(instanceRef.get());
return tryInvoke(origin, instanceRef.get(), arguments);
}
@@ -243,10 +276,6 @@ public boolean isInvokable() {
@Override
public Object invoke() throws Throwable {
Method method = origin.getJavaMethod();
- if (!Modifier.isPublic(
- method.getDeclaringClass().getModifiers() & method.getModifiers())) {
- method.setAccessible(true);
- }
MockMethodDispatcher mockMethodDispatcher =
MockMethodDispatcher.get(identifier, instanceRef.get());
if (!(mockMethodDispatcher instanceof MockMethodAdvice)) {
@@ -288,9 +317,6 @@ public boolean isInvokable() {
@Override
public Object invoke() throws Throwable {
- if (!Modifier.isPublic(type.getModifiers() & origin.getModifiers())) {
- origin.setAccessible(true);
- }
selfCallInfo.set(type);
return tryInvoke(origin, null, arguments);
}
@@ -298,8 +324,9 @@ public Object invoke() throws Throwable {
private static Object tryInvoke(Method origin, Object instance, Object[] arguments)
throws Throwable {
+ MemberAccessor accessor = Plugins.getMemberAccessor();
try {
- return origin.invoke(instance, arguments);
+ return accessor.invoke(origin, instance, arguments);
} catch (InvocationTargetException exception) {
Throwable cause = exception.getCause();
new ConditionalStackTraceFilter()
@@ -344,6 +371,297 @@ boolean checkSelfCall(Object value) {
}
}
+ static class ConstructorShortcut
+ implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
+
+ private final String identifier;
+
+ ConstructorShortcut(String identifier) {
+ this.identifier = identifier;
+ }
+
+ @Override
+ public MethodVisitor wrap(
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ if (instrumentedMethod.isConstructor() && !instrumentedType.represents(Object.class)) {
+ MethodList constructors =
+ instrumentedType
+ .getSuperClass()
+ .asErasure()
+ .getDeclaredMethods()
+ .filter(isConstructor().and(not(isPrivate())));
+ int arguments = Integer.MAX_VALUE;
+ boolean visible = false;
+ MethodDescription.InDefinedShape current = null;
+ for (MethodDescription.InDefinedShape constructor : constructors) {
+ if (constructor.getParameters().size() < arguments
+ && (!visible || constructor.isPackagePrivate())) {
+ current = constructor;
+ visible = constructor.isPackagePrivate();
+ }
+ }
+ if (current != null) {
+ final MethodDescription.InDefinedShape selected = current;
+ return new MethodVisitor(OpenedClassReader.ASM_API, methodVisitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ /*
+ * The byte code that is added to the start of the method is roughly equivalent to
+ * the following byte code for a hypothetical constructor of class Current:
+ *
+ * if (MockMethodDispatcher.isConstructorMock(, Current.class) {
+ * super();
+ * Current o = (Current) MockMethodDispatcher.handleConstruction(Current.class,
+ * this,
+ * new Object[] {argument1, argument2, ...},
+ * new String[] {argumentType1, argumentType2, ...});
+ * if (o != null) {
+ * this.field = o.field; // for each declared field
+ * }
+ * return;
+ * }
+ *
+ * This avoids the invocation of the original constructor chain but fullfils the
+ * verifier requirement to invoke a super constructor.
+ */
+ Label label = new Label();
+ super.visitLdcInsn(identifier);
+ if (implementationContext
+ .getClassFileVersion()
+ .isAtLeast(ClassFileVersion.JAVA_V5)) {
+ super.visitLdcInsn(Type.getType(instrumentedType.getDescriptor()));
+ } else {
+ super.visitLdcInsn(instrumentedType.getName());
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(
+ Type.getType(Class.class),
+ Type.getType(String.class)),
+ false);
+ }
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ Type.getInternalName(MockMethodDispatcher.class),
+ "isConstructorMock",
+ Type.getMethodDescriptor(
+ Type.BOOLEAN_TYPE,
+ Type.getType(String.class),
+ Type.getType(Class.class)),
+ false);
+ super.visitInsn(Opcodes.ICONST_0);
+ super.visitJumpInsn(Opcodes.IF_ICMPEQ, label);
+ super.visitVarInsn(Opcodes.ALOAD, 0);
+ for (TypeDescription type :
+ selected.getParameters().asTypeList().asErasures()) {
+ if (type.represents(boolean.class)
+ || type.represents(byte.class)
+ || type.represents(short.class)
+ || type.represents(char.class)
+ || type.represents(int.class)) {
+ super.visitInsn(Opcodes.ICONST_0);
+ } else if (type.represents(long.class)) {
+ super.visitInsn(Opcodes.LCONST_0);
+ } else if (type.represents(float.class)) {
+ super.visitInsn(Opcodes.FCONST_0);
+ } else if (type.represents(double.class)) {
+ super.visitInsn(Opcodes.DCONST_0);
+ } else {
+ super.visitInsn(Opcodes.ACONST_NULL);
+ }
+ }
+ super.visitMethodInsn(
+ Opcodes.INVOKESPECIAL,
+ selected.getDeclaringType().getInternalName(),
+ selected.getInternalName(),
+ selected.getDescriptor(),
+ false);
+ super.visitLdcInsn(identifier);
+ if (implementationContext
+ .getClassFileVersion()
+ .isAtLeast(ClassFileVersion.JAVA_V5)) {
+ super.visitLdcInsn(Type.getType(instrumentedType.getDescriptor()));
+ } else {
+ super.visitLdcInsn(instrumentedType.getName());
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(
+ Type.getType(Class.class),
+ Type.getType(String.class)),
+ false);
+ }
+ super.visitVarInsn(Opcodes.ALOAD, 0);
+ super.visitLdcInsn(instrumentedMethod.getParameters().size());
+ super.visitTypeInsn(
+ Opcodes.ANEWARRAY, Type.getInternalName(Object.class));
+ int index = 0;
+ for (ParameterDescription parameter :
+ instrumentedMethod.getParameters()) {
+ super.visitInsn(Opcodes.DUP);
+ super.visitLdcInsn(index++);
+ Type type =
+ Type.getType(
+ parameter.getType().asErasure().getDescriptor());
+ super.visitVarInsn(
+ type.getOpcode(Opcodes.ILOAD), parameter.getOffset());
+ if (parameter.getType().isPrimitive()) {
+ Type wrapper =
+ Type.getType(
+ parameter
+ .getType()
+ .asErasure()
+ .asBoxed()
+ .getDescriptor());
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ wrapper.getInternalName(),
+ "valueOf",
+ Type.getMethodDescriptor(wrapper, type),
+ false);
+ }
+ super.visitInsn(Opcodes.AASTORE);
+ }
+ index = 0;
+ super.visitLdcInsn(instrumentedMethod.getParameters().size());
+ super.visitTypeInsn(
+ Opcodes.ANEWARRAY, Type.getInternalName(String.class));
+ for (TypeDescription typeDescription :
+ instrumentedMethod.getParameters().asTypeList().asErasures()) {
+ super.visitInsn(Opcodes.DUP);
+ super.visitLdcInsn(index++);
+ super.visitLdcInsn(typeDescription.getName());
+ super.visitInsn(Opcodes.AASTORE);
+ }
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ Type.getInternalName(MockMethodDispatcher.class),
+ "handleConstruction",
+ Type.getMethodDescriptor(
+ Type.getType(Object.class),
+ Type.getType(String.class),
+ Type.getType(Class.class),
+ Type.getType(Object.class),
+ Type.getType(Object[].class),
+ Type.getType(String[].class)),
+ false);
+ FieldList fields =
+ instrumentedType.getDeclaredFields().filter(not(isStatic()));
+ super.visitTypeInsn(
+ Opcodes.CHECKCAST, instrumentedType.getInternalName());
+ super.visitInsn(Opcodes.DUP);
+ Label noSpy = new Label();
+ super.visitJumpInsn(Opcodes.IFNULL, noSpy);
+ for (FieldDescription field : fields) {
+ super.visitInsn(Opcodes.DUP);
+ super.visitFieldInsn(
+ Opcodes.GETFIELD,
+ instrumentedType.getInternalName(),
+ field.getInternalName(),
+ field.getDescriptor());
+ super.visitVarInsn(Opcodes.ALOAD, 0);
+ super.visitInsn(
+ field.getType().getStackSize() == StackSize.DOUBLE
+ ? Opcodes.DUP_X2
+ : Opcodes.DUP_X1);
+ super.visitInsn(Opcodes.POP);
+ super.visitFieldInsn(
+ Opcodes.PUTFIELD,
+ instrumentedType.getInternalName(),
+ field.getInternalName(),
+ field.getDescriptor());
+ }
+ super.visitLabel(noSpy);
+ if (implementationContext
+ .getClassFileVersion()
+ .isAtLeast(ClassFileVersion.JAVA_V6)) {
+ Object[] locals =
+ toFrames(
+ instrumentedType.getInternalName(),
+ instrumentedMethod
+ .getParameters()
+ .asTypeList()
+ .asErasures());
+ super.visitFrame(
+ Opcodes.F_FULL,
+ locals.length,
+ locals,
+ 1,
+ new Object[] {instrumentedType.getInternalName()});
+ }
+ super.visitInsn(Opcodes.POP);
+ super.visitInsn(Opcodes.RETURN);
+ super.visitLabel(label);
+ if (implementationContext
+ .getClassFileVersion()
+ .isAtLeast(ClassFileVersion.JAVA_V6)) {
+ Object[] locals =
+ toFrames(
+ Opcodes.UNINITIALIZED_THIS,
+ instrumentedMethod
+ .getParameters()
+ .asTypeList()
+ .asErasures());
+ super.visitFrame(
+ Opcodes.F_FULL, locals.length, locals, 0, new Object[0]);
+ }
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ int prequel = Math.max(5, selected.getStackSize());
+ for (ParameterDescription parameter :
+ instrumentedMethod.getParameters()) {
+ prequel =
+ Math.max(
+ prequel,
+ 6 + parameter.getType().getStackSize().getSize());
+ prequel = Math.max(prequel, 8);
+ }
+ super.visitMaxs(Math.max(maxStack, prequel), maxLocals);
+ }
+ };
+ }
+ }
+ return methodVisitor;
+ }
+
+ private static Object[] toFrames(Object self, List types) {
+ Object[] frames = new Object[1 + types.size()];
+ frames[0] = self;
+ int index = 0;
+ for (TypeDescription type : types) {
+ Object frame;
+ if (type.represents(boolean.class)
+ || type.represents(byte.class)
+ || type.represents(short.class)
+ || type.represents(char.class)
+ || type.represents(int.class)) {
+ frame = Opcodes.INTEGER;
+ } else if (type.represents(long.class)) {
+ frame = Opcodes.LONG;
+ } else if (type.represents(float.class)) {
+ frame = Opcodes.FLOAT;
+ } else if (type.represents(double.class)) {
+ frame = Opcodes.DOUBLE;
+ } else {
+ frame = type.getInternalName();
+ }
+ frames[++index] = frame;
+ }
+ return frames;
+ }
+ }
+
@Retention(RetentionPolicy.RUNTIME)
@interface Identifier {}
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
index 4a65643bc5..cd2b177640 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java
@@ -208,6 +208,12 @@ public void mockClassStatic(Class> type) {
throw new MockitoException("The subclass byte code generator cannot create static mocks");
}
+ @Override
+ public void mockClassConstruction(Class> type) {
+ throw new MockitoException(
+ "The subclass byte code generator cannot create construction mocks");
+ }
+
private Collection> getAllTypes(Class type) {
Collection> supertypes = new LinkedList>();
supertypes.add(type);
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
index 76bf44dca8..b3e70c7da7 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java
@@ -62,6 +62,11 @@ public void mockClassStatic(Class> type) {
bytecodeGenerator.mockClassStatic(type);
}
+ @Override
+ public void mockClassConstruction(Class> type) {
+ bytecodeGenerator.mockClassConstruction(type);
+ }
+
private static class MockitoMockKey extends TypeCache.SimpleKey {
private final SerializableMode serializableMode;
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.java b/src/main/java/org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.java
index f1b49051e0..3be8501574 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher.java
@@ -36,12 +36,32 @@ public static void set(String identifier, MockMethodDispatcher dispatcher) {
DISPATCHERS.putIfAbsent(identifier, dispatcher);
}
+ @SuppressWarnings("unused")
+ public static boolean isConstructorMock(String identifier, Class> type) {
+ return DISPATCHERS.get(identifier).isConstructorMock(type);
+ }
+
+ @SuppressWarnings("unused")
+ public static Object handleConstruction(
+ String identifier,
+ Class> type,
+ Object object,
+ Object[] arguments,
+ String[] parameterTypeNames) {
+ return DISPATCHERS
+ .get(identifier)
+ .handleConstruction(type, object, arguments, parameterTypeNames);
+ }
+
public abstract Callable> handle(Object instance, Method origin, Object[] arguments)
throws Throwable;
public abstract Callable> handleStatic(Class> type, Method origin, Object[] arguments)
throws Throwable;
+ public abstract Object handleConstruction(
+ Class> type, Object object, Object[] arguments, String[] parameterTypeNames);
+
public abstract boolean isMock(Object instance);
public abstract boolean isMocked(Object instance);
@@ -49,4 +69,6 @@ public abstract Callable> handleStatic(Class> type, Method origin, Object[]
public abstract boolean isMockedStatic(Class> type);
public abstract boolean isOverridden(Object instance, Method origin);
+
+ public abstract boolean isConstructorMock(Class> type);
}
diff --git a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
index 94e12fa86a..13f11d7419 100644
--- a/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
+++ b/src/main/java/org/mockito/internal/creation/instance/ConstructorInstantiator.java
@@ -14,8 +14,9 @@
import org.mockito.creation.instance.InstantiationException;
import org.mockito.creation.instance.Instantiator;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Primitives;
-import org.mockito.internal.util.reflection.AccessibilityChanger;
+import org.mockito.plugins.MemberAccessor;
public class ConstructorInstantiator implements Instantiator {
@@ -64,9 +65,8 @@ private T withParams(Class cls, Object... params) {
private static T invokeConstructor(Constructor> constructor, Object... params)
throws java.lang.InstantiationException, IllegalAccessException,
InvocationTargetException {
- AccessibilityChanger accessibility = new AccessibilityChanger();
- accessibility.enableAccess(constructor);
- return (T) constructor.newInstance(params);
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ return (T) accessor.newInstance(constructor, params);
}
private InstantiationException paramsException(Class> cls, Exception e) {
diff --git a/src/main/java/org/mockito/internal/junit/util/JUnitFailureHacker.java b/src/main/java/org/mockito/internal/junit/util/JUnitFailureHacker.java
index 89bd4f1f4b..d6e784c647 100644
--- a/src/main/java/org/mockito/internal/junit/util/JUnitFailureHacker.java
+++ b/src/main/java/org/mockito/internal/junit/util/JUnitFailureHacker.java
@@ -7,7 +7,9 @@
import java.lang.reflect.Field;
import org.junit.runner.notification.Failure;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
+import org.mockito.plugins.MemberAccessor;
@Deprecated
public class JUnitFailureHacker {
@@ -36,11 +38,11 @@ private boolean isEmpty(String warnings) {
}
private static Object getInternalState(Object target, String field) {
+ MemberAccessor accessor = Plugins.getMemberAccessor();
Class> c = target.getClass();
try {
Field f = getFieldFromHierarchy(c, field);
- f.setAccessible(true);
- return f.get(target);
+ return accessor.get(f, target);
} catch (Exception e) {
throw new RuntimeException(
"Unable to get internal state on a private field. Please report to mockito mailing list.",
@@ -49,11 +51,11 @@ private static Object getInternalState(Object target, String field) {
}
private static void setInternalState(Object target, String field, Object value) {
+ MemberAccessor accessor = Plugins.getMemberAccessor();
Class> c = target.getClass();
try {
Field f = getFieldFromHierarchy(c, field);
- f.setAccessible(true);
- f.set(target, value);
+ accessor.set(f, target, value);
} catch (Exception e) {
throw new RuntimeException(
"Unable to set internal state on a private field. Please report to mockito mailing list.",
diff --git a/src/main/java/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java b/src/main/java/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java
index 9a43040e34..1789a5d7a3 100644
--- a/src/main/java/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java
+++ b/src/main/java/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java
@@ -4,7 +4,9 @@
*/
package org.mockito.internal.matchers.apachecommons;
-import java.lang.reflect.AccessibleObject;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.plugins.MemberAccessor;
+
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -253,20 +255,16 @@ public static boolean reflectionEquals(
return false;
}
EqualsBuilder equalsBuilder = new EqualsBuilder();
- try {
- reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
- while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
- testClass = testClass.getSuperclass();
- reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
- }
- } catch (IllegalArgumentException e) {
- // In this case, we tried to test a subclass vs. a superclass and
- // the subclass has ivars or the ivars are transient and
- // we are testing transients.
- // If a subclass has ivars that we are trying to test them, we get an
- // exception and we know that the objects are not equal.
+ if (reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields)) {
return false;
}
+ while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
+ testClass = testClass.getSuperclass();
+ if (reflectionAppend(
+ lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields)) {
+ return false;
+ }
+ }
return equalsBuilder.isEquals();
}
@@ -281,7 +279,7 @@ public static boolean reflectionEquals(
* @param useTransients whether to test transient fields
* @param excludeFields array of field names to exclude from testing
*/
- private static void reflectionAppend(
+ private static boolean reflectionAppend(
Object lhs,
Object rhs,
Class> clazz,
@@ -293,7 +291,7 @@ private static void reflectionAppend(
excludeFields != null
? Arrays.asList(excludeFields)
: Collections.emptyList();
- AccessibleObject.setAccessible(fields, true);
+ MemberAccessor accessor = Plugins.getMemberAccessor();
for (int i = 0; i < fields.length && builder.isEquals; i++) {
Field f = fields[i];
if (!excludedFieldList.contains(f.getName())
@@ -301,14 +299,18 @@ private static void reflectionAppend(
&& (useTransients || !Modifier.isTransient(f.getModifiers()))
&& (!Modifier.isStatic(f.getModifiers()))) {
try {
- builder.append(f.get(lhs), f.get(rhs));
- } catch (IllegalAccessException e) {
- // this can't happen. Would get a Security exception instead
- // throw a runtime exception in case the impossible happens.
- throw new InternalError("Unexpected IllegalAccessException");
+ builder.append(accessor.get(f, lhs), accessor.get(f, rhs));
+ } catch (RuntimeException | IllegalAccessException ignored) {
+ // In this case, we tried to test a subclass vs. a superclass and
+ // the subclass has ivars or the ivars are transient and we are
+ // testing transients. If a subclass has ivars that we are trying
+ // to test them, we get an exception and we know that the objects
+ // are not equal.
+ return true;
}
}
}
+ return false;
}
// -------------------------------------------------------------------------
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
index 0df0f6c3ef..0f2aaa3975 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
@@ -11,8 +11,10 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.InvocationOnMock;
+import org.mockito.plugins.MemberAccessor;
import org.mockito.stubbing.Answer;
/**
@@ -41,13 +43,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
mockMethod, delegateMethod, invocation.getMock(), delegatedObject);
}
+ MemberAccessor accessor = Plugins.getMemberAccessor();
Object[] rawArguments = ((Invocation) invocation).getRawArguments();
- try {
- delegateMethod.setAccessible(true);
- } catch (SecurityException ignore) {
- // try to invoke anyway
- }
- return delegateMethod.invoke(delegatedObject, rawArguments);
+ return accessor.invoke(delegateMethod, delegatedObject, rawArguments);
} catch (NoSuchMethodException e) {
throw delegatedMethodDoesNotExistOnDelegate(
mockMethod, invocation.getMock(), delegatedObject);
diff --git a/src/main/java/org/mockito/internal/util/JavaEightUtil.java b/src/main/java/org/mockito/internal/util/JavaEightUtil.java
index bc39d44b68..20c6e80c3b 100644
--- a/src/main/java/org/mockito/internal/util/JavaEightUtil.java
+++ b/src/main/java/org/mockito/internal/util/JavaEightUtil.java
@@ -181,7 +181,7 @@ private static Object invokeNullaryFactoryMethod(final String fqcn, final String
private static Object getStaticFieldValue(final String fqcn, final String fieldName) {
try {
final Class> type = getClass(fqcn);
- final Field field = type.getDeclaredField(fieldName);
+ final Field field = type.getField(fieldName);
return field.get(null);
// any exception is really unexpected since the type name has
// already been verified
diff --git a/src/main/java/org/mockito/internal/util/MockUtil.java b/src/main/java/org/mockito/internal/util/MockUtil.java
index 4767ef9445..46e782cba0 100644
--- a/src/main/java/org/mockito/internal/util/MockUtil.java
+++ b/src/main/java/org/mockito/internal/util/MockUtil.java
@@ -4,8 +4,7 @@
*/
package org.mockito.internal.util;
-import static org.mockito.internal.handler.MockHandlerFactory.createMockHandler;
-
+import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -18,6 +17,10 @@
import org.mockito.plugins.MockMaker;
import org.mockito.plugins.MockMaker.TypeMockability;
+import java.util.function.Function;
+
+import static org.mockito.internal.handler.MockHandlerFactory.createMockHandler;
+
@SuppressWarnings("unchecked")
public class MockUtil {
@@ -32,11 +35,21 @@ public static TypeMockability typeMockabilityOf(Class> type) {
public static T createMock(MockCreationSettings settings) {
MockHandler mockHandler = createMockHandler(settings);
- T mock = mockMaker.createMock(settings, mockHandler);
-
Object spiedInstance = settings.getSpiedInstance();
+
+ T mock;
if (spiedInstance != null) {
- new LenientCopyTool().copyToMock(spiedInstance, mock);
+ mock =
+ mockMaker
+ .createSpy(settings, mockHandler, (T) spiedInstance)
+ .orElseGet(
+ () -> {
+ T instance = mockMaker.createMock(settings, mockHandler);
+ new LenientCopyTool().copyToMock(spiedInstance, instance);
+ return instance;
+ });
+ } else {
+ mock = mockMaker.createMock(settings, mockHandler);
}
return mock;
@@ -108,4 +121,14 @@ public static MockMaker.StaticMockControl createStaticMock(
MockHandler handler = createMockHandler(settings);
return mockMaker.createStaticMock(type, settings, handler);
}
+
+ public static MockMaker.ConstructionMockControl createConstructionMock(
+ Class type,
+ Function> settingsFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ Function> handlerFactory =
+ context -> createMockHandler(settingsFactory.apply(context));
+ return mockMaker.createConstructionMock(
+ type, settingsFactory, handlerFactory, mockInitializer);
+ }
}
diff --git a/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java b/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java
deleted file mode 100644
index 33725e0137..0000000000
--- a/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2007 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-package org.mockito.internal.util.reflection;
-
-import java.lang.reflect.AccessibleObject;
-
-public class AccessibilityChanger {
-
- private Boolean wasAccessible = null;
-
- /**
- * safely disables access
- */
- public void safelyDisableAccess(AccessibleObject accessibleObject) {
- assert wasAccessible != null : "accessibility info shall not be null";
- try {
- accessibleObject.setAccessible(wasAccessible);
- } catch (Throwable t) {
- // ignore
- }
- }
-
- /**
- * changes the accessibleObject accessibility and returns true if accessibility was changed
- */
- public void enableAccess(AccessibleObject accessibleObject) {
- wasAccessible = accessibleObject.isAccessible();
- accessibleObject.setAccessible(true);
- }
-}
diff --git a/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java b/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java
index f91a35c202..7a33dcec7b 100644
--- a/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java
+++ b/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java
@@ -4,6 +4,9 @@
*/
package org.mockito.internal.util.reflection;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.plugins.MemberAccessor;
+
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -51,13 +54,11 @@ public BeanPropertySetter(final Object target, final Field propertyField) {
*/
public boolean set(final Object value) {
- AccessibilityChanger changer = new AccessibilityChanger();
+ MemberAccessor accessor = Plugins.getMemberAccessor();
Method writeMethod = null;
try {
writeMethod = target.getClass().getMethod(setterName(field.getName()), field.getType());
-
- changer.enableAccess(writeMethod);
- writeMethod.invoke(target, value);
+ accessor.invoke(writeMethod, target, value);
return true;
} catch (InvocationTargetException e) {
throw new RuntimeException(
@@ -83,10 +84,6 @@ public boolean set(final Object value) {
e);
} catch (NoSuchMethodException e) {
reportNoSetterFound();
- } finally {
- if (writeMethod != null) {
- changer.safelyDisableAccess(writeMethod);
- }
}
reportNoSetterFound();
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java b/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java
deleted file mode 100644
index 67f7203944..0000000000
--- a/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (c) 2007 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-package org.mockito.internal.util.reflection;
-
-import java.lang.reflect.Field;
-
-public class FieldCopier {
-
- public void copyValue(T from, T to, Field field) throws IllegalAccessException {
- Object value = field.get(from);
- field.set(to, value);
- }
-}
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 a5c5d708b5..53bc8a1f6a 100644
--- a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java
+++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java
@@ -4,9 +4,10 @@
*/
package org.mockito.internal.util.reflection;
-import static java.lang.reflect.Modifier.isStatic;
-
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.internal.util.MockUtil;
+import org.mockito.plugins.MemberAccessor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -17,8 +18,7 @@
import java.util.Comparator;
import java.util.List;
-import org.mockito.exceptions.base.MockitoException;
-import org.mockito.internal.util.MockUtil;
+import static java.lang.reflect.Modifier.isStatic;
/**
* Initialize a field with type instance if a default constructor can be found.
@@ -87,9 +87,6 @@ private FieldInitializer(Object fieldOwner, Field field, ConstructorInstantiator
* @return Actual field instance.
*/
public FieldInitializationReport initialize() {
- final AccessibilityChanger changer = new AccessibilityChanger();
- changer.enableAccess(field);
-
try {
return acquireFieldInstance();
} catch (IllegalAccessException e) {
@@ -100,8 +97,6 @@ public FieldInitializationReport initialize() {
+ field.getType().getSimpleName()
+ "'",
e);
- } finally {
- changer.safelyDisableAccess(field);
}
}
@@ -142,7 +137,8 @@ private void checkNotEnum(Field field) {
}
private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException {
- Object fieldInstance = field.get(fieldOwner);
+ final MemberAccessor accessor = Plugins.getMemberAccessor();
+ Object fieldInstance = accessor.get(field, fieldOwner);
if (fieldInstance != null) {
return new FieldInitializationReport(fieldInstance, false, false);
}
@@ -198,17 +194,15 @@ static class NoArgConstructorInstantiator implements ConstructorInstantiator {
}
public FieldInitializationReport instantiate() {
- final AccessibilityChanger changer = new AccessibilityChanger();
- Constructor> constructor = null;
+ final MemberAccessor invoker = Plugins.getMemberAccessor();
try {
- constructor = field.getType().getDeclaredConstructor();
- changer.enableAccess(constructor);
+ Constructor> constructor = field.getType().getDeclaredConstructor();
final Object[] noArg = new Object[0];
- Object newFieldInstance = constructor.newInstance(noArg);
- setField(testClass, field, newFieldInstance);
+ Object newFieldInstance = invoker.newInstance(constructor, noArg);
+ invoker.set(field, testClass, newFieldInstance);
- return new FieldInitializationReport(field.get(testClass), true, false);
+ return new FieldInitializationReport(invoker.get(field, testClass), true, false);
} catch (NoSuchMethodException e) {
throw new MockitoException(
"the type '"
@@ -230,10 +224,6 @@ public FieldInitializationReport instantiate() {
throw new MockitoException(
"IllegalAccessException (see the stack trace for cause): " + e.toString(),
e);
- } finally {
- if (constructor != null) {
- changer.safelyDisableAccess(constructor);
- }
}
}
}
@@ -289,18 +279,14 @@ private int countMockableParams(Constructor> constructor) {
}
public FieldInitializationReport instantiate() {
- final AccessibilityChanger changer = new AccessibilityChanger();
- Constructor> constructor = null;
+ final MemberAccessor accessor = Plugins.getMemberAccessor();
+ Constructor> constructor = biggestConstructor(field.getType());
+ final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes());
try {
- constructor = biggestConstructor(field.getType());
- changer.enableAccess(constructor);
-
- final Object[] args =
- argResolver.resolveTypeInstances(constructor.getParameterTypes());
- Object newFieldInstance = constructor.newInstance(args);
- setField(testClass, field, newFieldInstance);
+ Object newFieldInstance = accessor.newInstance(constructor, args);
+ accessor.set(field, testClass, newFieldInstance);
- return new FieldInitializationReport(field.get(testClass), false, true);
+ return new FieldInitializationReport(accessor.get(field, testClass), false, true);
} catch (IllegalArgumentException e) {
throw new MockitoException(
"internal error : argResolver provided incorrect types for constructor "
@@ -323,10 +309,6 @@ public FieldInitializationReport instantiate() {
throw new MockitoException(
"IllegalAccessException (see the stack trace for cause): " + e.toString(),
e);
- } finally {
- if (constructor != null) {
- changer.safelyDisableAccess(constructor);
- }
}
}
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldReader.java b/src/main/java/org/mockito/internal/util/reflection/FieldReader.java
index 707eee6515..e202c714a0 100644
--- a/src/main/java/org/mockito/internal/util/reflection/FieldReader.java
+++ b/src/main/java/org/mockito/internal/util/reflection/FieldReader.java
@@ -7,17 +7,18 @@
import java.lang.reflect.Field;
import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.plugins.MemberAccessor;
public class FieldReader {
final Object target;
final Field field;
- final AccessibilityChanger changer = new AccessibilityChanger();
+ final MemberAccessor accessor = Plugins.getMemberAccessor();
public FieldReader(Object target, Field field) {
this.target = target;
this.field = field;
- changer.enableAccess(field);
}
public boolean isNull() {
@@ -26,7 +27,7 @@ public boolean isNull() {
public Object read() {
try {
- return field.get(target);
+ return accessor.get(field, target);
} catch (Exception e) {
throw new MockitoException(
"Cannot read state from field: " + field + ", on instance: " + target);
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java b/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java
deleted file mode 100644
index 46f906d9bf..0000000000
--- a/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2007 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-package org.mockito.internal.util.reflection;
-
-import java.lang.reflect.Field;
-
-public class FieldSetter {
-
- private FieldSetter() {}
-
- public static void setField(Object target, Field field, Object value) {
- AccessibilityChanger changer = new AccessibilityChanger();
- changer.enableAccess(field);
- try {
- field.set(target, value);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "Access not authorized on field '"
- + field
- + "' of object '"
- + target
- + "' with value: '"
- + value
- + "'",
- e);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(
- "Wrong argument on field '"
- + field
- + "' of object '"
- + target
- + "' with value: '"
- + value
- + "', \n"
- + "reason : "
- + e.getMessage(),
- e);
- }
- changer.safelyDisableAccess(field);
- }
-}
diff --git a/src/main/java/org/mockito/internal/util/reflection/InstanceField.java b/src/main/java/org/mockito/internal/util/reflection/InstanceField.java
index 5585874f46..03cd02c051 100644
--- a/src/main/java/org/mockito/internal/util/reflection/InstanceField.java
+++ b/src/main/java/org/mockito/internal/util/reflection/InstanceField.java
@@ -4,12 +4,13 @@
*/
package org.mockito.internal.util.reflection;
-import static org.mockito.internal.util.reflection.FieldSetter.setField;
-
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Checks;
+import org.mockito.plugins.MemberAccessor;
/**
* Represents an accessible instance field.
@@ -47,10 +48,14 @@ public Object read() {
* Set the given value to the field of this instance.
*
* @param value The value that should be written to the field.
- * @see FieldSetter
*/
public void set(Object value) {
- setField(instance, field, value);
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ try {
+ accessor.set(field, instance, value);
+ } catch (IllegalAccessException e) {
+ throw new MockitoException("Access to " + field + " was denied", e);
+ }
}
/**
diff --git a/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java b/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java
new file mode 100644
index 0000000000..c74c79efa6
--- /dev/null
+++ b/src/main/java/org/mockito/internal/util/reflection/InstrumentationMemberAccessor.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodCall;
+import org.mockito.exceptions.base.MockitoInitializationException;
+import org.mockito.plugins.MemberAccessor;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.*;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.mockito.internal.util.StringUtil.join;
+
+class InstrumentationMemberAccessor implements MemberAccessor {
+
+ private static final Map, Class>> WRAPPERS = new HashMap<>();
+
+ private static final Instrumentation INSTRUMENTATION;
+ private static final Dispatcher DISPATCHER;
+
+ private static final Throwable INITIALIZATION_ERROR;
+
+ static {
+ WRAPPERS.put(boolean.class, Boolean.class);
+ WRAPPERS.put(byte.class, Byte.class);
+ WRAPPERS.put(short.class, Short.class);
+ WRAPPERS.put(char.class, Character.class);
+ WRAPPERS.put(int.class, Integer.class);
+ WRAPPERS.put(long.class, Long.class);
+ WRAPPERS.put(float.class, Float.class);
+ WRAPPERS.put(double.class, Double.class);
+ Instrumentation instrumentation;
+ Dispatcher dispatcher;
+ Throwable throwable;
+ try {
+ instrumentation = ByteBuddyAgent.install();
+ // We need to generate a dispatcher instance that is located in a distinguished class
+ // loader to create a unique (unnamed) module to which we can open other packages to.
+ // This way, we assure that classes within Mockito's module (which might be a shared,
+ // unnamed module) do not face escalated privileges where tests might pass that would
+ // otherwise fail without Mockito's opening.
+ dispatcher =
+ new ByteBuddy()
+ .subclass(Dispatcher.class)
+ .method(named("getLookup"))
+ .intercept(MethodCall.invoke(MethodHandles.class.getMethod("lookup")))
+ .method(named("getModule"))
+ .intercept(
+ MethodCall.invoke(Class.class.getMethod("getModule"))
+ .onMethodCall(
+ MethodCall.invoke(
+ Object.class.getMethod("getClass"))))
+ .method(named("setAccessible"))
+ .intercept(
+ MethodCall.invoke(
+ AccessibleObject.class.getMethod(
+ "setAccessible", boolean.class))
+ .onArgument(0)
+ .withArgument(1))
+ .make()
+ .load(
+ InstrumentationMemberAccessor.class.getClassLoader(),
+ ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getConstructor()
+ .newInstance();
+ throwable = null;
+ } catch (Throwable t) {
+ instrumentation = null;
+ dispatcher = null;
+ throwable = t;
+ }
+ INSTRUMENTATION = instrumentation;
+ DISPATCHER = dispatcher;
+ INITIALIZATION_ERROR = throwable;
+ }
+
+ private final MethodHandle getModule, isOpen, redefineModule, privateLookupIn;
+
+ InstrumentationMemberAccessor() {
+ if (INITIALIZATION_ERROR != null) {
+ throw new MockitoInitializationException(
+ join(
+ "Could not initialize the Mockito instrumentation member accessor",
+ "",
+ "This is unexpected on JVMs from Java 9 or later - possibly, the instrumentation API could not be resolved"),
+ INITIALIZATION_ERROR);
+ }
+ try {
+ Class> module = Class.forName("java.lang.Module");
+ getModule =
+ MethodHandles.publicLookup()
+ .findVirtual(Class.class, "getModule", MethodType.methodType(module));
+ isOpen =
+ MethodHandles.publicLookup()
+ .findVirtual(
+ module,
+ "isOpen",
+ MethodType.methodType(boolean.class, String.class, module));
+ redefineModule =
+ MethodHandles.publicLookup()
+ .findVirtual(
+ Instrumentation.class,
+ "redefineModule",
+ MethodType.methodType(
+ void.class,
+ module,
+ Set.class,
+ Map.class,
+ Map.class,
+ Set.class,
+ Map.class));
+ privateLookupIn =
+ MethodHandles.publicLookup()
+ .findStatic(
+ MethodHandles.class,
+ "privateLookupIn",
+ MethodType.methodType(
+ MethodHandles.Lookup.class,
+ Class.class,
+ MethodHandles.Lookup.class));
+ } catch (Throwable t) {
+ throw new MockitoInitializationException(
+ "Could not resolve instrumentation invoker", t);
+ }
+ }
+
+ @Override
+ public Object newInstance(Constructor> constructor, Object... arguments)
+ throws InstantiationException, InvocationTargetException {
+ if (Modifier.isAbstract(constructor.getDeclaringClass().getModifiers())) {
+ throw new InstantiationException(
+ "Cannot instantiate abstract " + constructor.getDeclaringClass().getTypeName());
+ }
+ assureArguments(constructor, null, null, arguments, constructor.getParameterTypes());
+ try {
+ Object module = getModule.bindTo(constructor.getDeclaringClass()).invokeWithArguments();
+ String packageName = constructor.getDeclaringClass().getPackage().getName();
+ assureOpen(module, packageName);
+ MethodHandle handle =
+ ((MethodHandles.Lookup)
+ privateLookupIn.invokeExact(
+ constructor.getDeclaringClass(),
+ DISPATCHER.getLookup()))
+ .unreflectConstructor(constructor);
+ try {
+ return handle.invokeWithArguments(arguments);
+ } catch (Throwable t) {
+ throw new InvocationTargetException(t);
+ }
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new IllegalStateException(
+ "Could not construct "
+ + constructor
+ + " with arguments "
+ + Arrays.toString(arguments),
+ t);
+ }
+ }
+
+ @Override
+ public Object invoke(Method method, Object target, Object... arguments)
+ throws InvocationTargetException {
+ assureArguments(
+ method,
+ Modifier.isStatic(method.getModifiers()) ? null : target,
+ method.getDeclaringClass(),
+ arguments,
+ method.getParameterTypes());
+ try {
+ Object module = getModule.bindTo(method.getDeclaringClass()).invokeWithArguments();
+ String packageName = method.getDeclaringClass().getPackage().getName();
+ assureOpen(module, packageName);
+ MethodHandle handle =
+ ((MethodHandles.Lookup)
+ privateLookupIn.invokeExact(
+ method.getDeclaringClass(), DISPATCHER.getLookup()))
+ .unreflect(method);
+ if (!Modifier.isStatic(method.getModifiers())) {
+ handle = handle.bindTo(target);
+ }
+ try {
+ return handle.invokeWithArguments(arguments);
+ } catch (Throwable t) {
+ throw new InvocationTargetException(t);
+ }
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new IllegalStateException(
+ "Could not invoke "
+ + method
+ + " on "
+ + target
+ + " with arguments "
+ + Arrays.toString(arguments),
+ t);
+ }
+ }
+
+ @Override
+ public Object get(Field field, Object target) {
+ assureArguments(
+ field,
+ Modifier.isStatic(field.getModifiers()) ? null : target,
+ field.getDeclaringClass(),
+ new Object[0],
+ new Class>[0]);
+ try {
+ Object module = getModule.bindTo(field.getDeclaringClass()).invokeWithArguments();
+ String packageName = field.getDeclaringClass().getPackage().getName();
+ assureOpen(module, packageName);
+ MethodHandle handle =
+ ((MethodHandles.Lookup)
+ privateLookupIn.invokeExact(
+ field.getDeclaringClass(), DISPATCHER.getLookup()))
+ .unreflectGetter(field);
+ if (!Modifier.isStatic(field.getModifiers())) {
+ handle = handle.bindTo(target);
+ }
+ return handle.invokeWithArguments();
+ } catch (Throwable t) {
+ throw new IllegalStateException("Could not read " + field + " on " + target, t);
+ }
+ }
+
+ @Override
+ public void set(Field field, Object target, Object value) throws IllegalAccessException {
+ assureArguments(
+ field,
+ Modifier.isStatic(field.getModifiers()) ? null : target,
+ field.getDeclaringClass(),
+ new Object[] {value},
+ new Class>[] {field.getType()});
+ boolean illegalAccess = false;
+ try {
+ Object module = getModule.bindTo(field.getDeclaringClass()).invokeWithArguments();
+ String packageName = field.getDeclaringClass().getPackage().getName();
+ assureOpen(module, packageName);
+ // Method handles do not allow setting final fields where setAccessible(true)
+ // is required before unreflecting.
+ boolean isFinal;
+ if (Modifier.isFinal(field.getModifiers())) {
+ isFinal = true;
+ try {
+ DISPATCHER.setAccessible(field, true);
+ } catch (Throwable ignored) {
+ illegalAccess =
+ true; // To distinguish from propagated illegal access exception.
+ throw new IllegalAccessException(
+ "Could not make final field " + field + " accessible");
+ }
+ } else {
+ isFinal = false;
+ }
+ try {
+ MethodHandle handle =
+ ((MethodHandles.Lookup)
+ privateLookupIn.invokeExact(
+ field.getDeclaringClass(), DISPATCHER.getLookup()))
+ .unreflectSetter(field);
+ if (!Modifier.isStatic(field.getModifiers())) {
+ handle = handle.bindTo(target);
+ }
+ handle.invokeWithArguments(value);
+ } finally {
+ if (isFinal) {
+ DISPATCHER.setAccessible(field, false);
+ }
+ }
+ } catch (Throwable t) {
+ if (illegalAccess) {
+ throw (IllegalAccessException) t;
+ } else {
+ throw new IllegalStateException("Could not read " + field + " on " + target, t);
+ }
+ }
+ }
+
+ private void assureOpen(Object module, String packageName) throws Throwable {
+ if (!(Boolean) isOpen.invokeWithArguments(module, packageName, DISPATCHER.getModule())) {
+ redefineModule
+ .bindTo(INSTRUMENTATION)
+ .invokeWithArguments(
+ module,
+ Collections.emptySet(),
+ Collections.emptyMap(),
+ Collections.singletonMap(
+ packageName, Collections.singleton(DISPATCHER.getModule())),
+ Collections.emptySet(),
+ Collections.emptyMap());
+ }
+ }
+
+ private static void assureArguments(
+ AccessibleObject target,
+ Object owner,
+ Class> type,
+ Object[] values,
+ Class>[] types) {
+ if (owner != null) {
+ if (!type.isAssignableFrom(owner.getClass())) {
+ throw new IllegalArgumentException("Cannot access " + target + " on " + owner);
+ }
+ }
+ if (types.length != values.length) {
+ throw new IllegalArgumentException(
+ "Incorrect number of arguments for "
+ + target
+ + ": expected "
+ + types.length
+ + " but recevied "
+ + values.length);
+ }
+ for (int index = 0; index < values.length; index++) {
+ if (values[index] == null) {
+ if (types[index].isPrimitive()) {
+ throw new IllegalArgumentException(
+ "Cannot assign null to primitive type "
+ + types[index].getTypeName()
+ + " for "
+ + index
+ + " parameter of "
+ + target);
+ }
+ } else {
+ Class> resolved = WRAPPERS.getOrDefault(types[index], types[index]);
+ if (!resolved.isAssignableFrom(values[index].getClass())) {
+ throw new IllegalArgumentException(
+ "Cannot assign value of type "
+ + values[index].getClass()
+ + " to "
+ + resolved
+ + " for "
+ + index
+ + " parameter of "
+ + target);
+ }
+ }
+ }
+ }
+
+ public interface Dispatcher {
+
+ MethodHandles.Lookup getLookup();
+
+ Object getModule();
+
+ void setAccessible(AccessibleObject target, boolean value);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java b/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java
index ccdd923835..95b30198db 100644
--- a/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java
+++ b/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java
@@ -4,13 +4,15 @@
*/
package org.mockito.internal.util.reflection;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.plugins.MemberAccessor;
+
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-@SuppressWarnings("unchecked")
public class LenientCopyTool {
- FieldCopier fieldCopier = new FieldCopier();
+ MemberAccessor accessor = Plugins.getMemberAccessor();
public void copyToMock(T from, T mock) {
copy(from, mock, from.getClass());
@@ -35,14 +37,11 @@ private void copyValues(T from, T mock, Class> classFrom) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
- AccessibilityChanger accessibilityChanger = new AccessibilityChanger();
try {
- accessibilityChanger.enableAccess(field);
- fieldCopier.copyValue(from, mock, field);
+ Object value = accessor.get(field, from);
+ accessor.set(field, mock, value);
} catch (Throwable t) {
// Ignore - be lenient - if some field cannot be copied then let's be it
- } finally {
- accessibilityChanger.safelyDisableAccess(field);
}
}
}
diff --git a/src/main/java/org/mockito/internal/util/reflection/ModuleMemberAccessor.java b/src/main/java/org/mockito/internal/util/reflection/ModuleMemberAccessor.java
new file mode 100644
index 0000000000..8be77860ad
--- /dev/null
+++ b/src/main/java/org/mockito/internal/util/reflection/ModuleMemberAccessor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import net.bytebuddy.ClassFileVersion;
+import org.mockito.plugins.MemberAccessor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ModuleMemberAccessor implements MemberAccessor {
+
+ private final MemberAccessor delegate;
+
+ public ModuleMemberAccessor() {
+ if (ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V9)) {
+ delegate = new InstrumentationMemberAccessor();
+ } else {
+ delegate = new ReflectionMemberAccessor();
+ }
+ }
+
+ @Override
+ public Object newInstance(Constructor> constructor, Object... arguments)
+ throws InstantiationException, InvocationTargetException, IllegalAccessException {
+ return delegate.newInstance(constructor, arguments);
+ }
+
+ @Override
+ public Object invoke(Method method, Object target, Object... arguments)
+ throws InvocationTargetException, IllegalAccessException {
+ return delegate.invoke(method, target, arguments);
+ }
+
+ @Override
+ public Object get(Field field, Object target) throws IllegalAccessException {
+ return delegate.get(field, target);
+ }
+
+ @Override
+ public void set(Field field, Object target, Object value) throws IllegalAccessException {
+ delegate.set(field, target, value);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/ReflectionMemberAccessor.java b/src/main/java/org/mockito/internal/util/reflection/ReflectionMemberAccessor.java
new file mode 100644
index 0000000000..e61a5bcc02
--- /dev/null
+++ b/src/main/java/org/mockito/internal/util/reflection/ReflectionMemberAccessor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import org.mockito.plugins.MemberAccessor;
+
+import java.lang.reflect.*;
+import java.util.Arrays;
+
+public class ReflectionMemberAccessor implements MemberAccessor {
+
+ @Override
+ public Object newInstance(Constructor> constructor, Object... arguments)
+ throws InstantiationException, InvocationTargetException, IllegalAccessException {
+ silentSetAccessible(constructor, true);
+ try {
+ return constructor.newInstance(arguments);
+ } catch (InvocationTargetException
+ | IllegalAccessException
+ | InstantiationException
+ | IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Failed to invoke " + constructor + " with " + Arrays.toString(arguments), e);
+ } finally {
+ silentSetAccessible(constructor, false);
+ }
+ }
+
+ @Override
+ public Object invoke(Method method, Object target, Object... arguments)
+ throws InvocationTargetException, IllegalAccessException {
+ silentSetAccessible(method, true);
+ try {
+ return method.invoke(target, arguments);
+ } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not invoke " + method + " on " + target, e);
+ } finally {
+ silentSetAccessible(method, false);
+ }
+ }
+
+ @Override
+ public Object get(Field field, Object target) throws IllegalAccessException {
+ silentSetAccessible(field, true);
+ try {
+ return field.get(target);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not read " + field + " from " + target);
+ } finally {
+ silentSetAccessible(field, false);
+ }
+ }
+
+ @Override
+ public void set(Field field, Object target, Object value) throws IllegalAccessException {
+ silentSetAccessible(field, true);
+ try {
+ field.set(target, value);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not write " + field + " to " + target, e);
+ } finally {
+ silentSetAccessible(field, false);
+ }
+ }
+
+ private static void silentSetAccessible(AccessibleObject object, boolean value) {
+ try {
+ object.setAccessible(value);
+ } catch (Exception ignored) {
+ }
+ }
+}
diff --git a/src/main/java/org/mockito/plugins/MemberAccessor.java b/src/main/java/org/mockito/plugins/MemberAccessor.java
new file mode 100644
index 0000000000..081814e1db
--- /dev/null
+++ b/src/main/java/org/mockito/plugins/MemberAccessor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.plugins;
+
+import org.mockito.Incubating;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A member accessor is responsible for invoking methods, constructors and for setting
+ * and reading field values.
+ */
+@Incubating
+public interface MemberAccessor {
+
+ Object newInstance(Constructor> constructor, Object... arguments)
+ throws InstantiationException, InvocationTargetException, IllegalAccessException;
+
+ Object invoke(Method method, Object target, Object... arguments)
+ throws InvocationTargetException, IllegalAccessException;
+
+ Object get(Field field, Object target) throws IllegalAccessException;
+
+ void set(Field field, Object target, Object value) throws IllegalAccessException;
+}
diff --git a/src/main/java/org/mockito/plugins/MockMaker.java b/src/main/java/org/mockito/plugins/MockMaker.java
index df3ff4210d..fbdaf1d0e6 100644
--- a/src/main/java/org/mockito/plugins/MockMaker.java
+++ b/src/main/java/org/mockito/plugins/MockMaker.java
@@ -5,11 +5,16 @@
package org.mockito.plugins;
import org.mockito.Incubating;
+import org.mockito.MockedConstruction;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
-import static org.mockito.internal.util.StringUtil.*;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import static org.mockito.internal.util.StringUtil.join;
/**
* The facility to create mocks.
@@ -69,6 +74,25 @@ public interface MockMaker {
*/
T createMock(MockCreationSettings settings, MockHandler handler);
+ /**
+ * By implementing this method, a mock maker can optionally support the creation of spies where all fields
+ * are set within a constructor. This avoids problems when creating spies of classes that declare
+ * effectively final instance fields where setting field values from outside the constructor is prohibited.
+ *
+ * @param settings Mock creation settings like type to mock, extra interfaces and so on.
+ * @param handler See {@link org.mockito.invocation.MockHandler}.
+ * Do not provide your own implementation at this time. Make sure your implementation of
+ * {@link #getHandler(Object)} will return this instance.
+ * @param instance The object to spy upon.
+ * @param Type of the mock to return, actually the settings.getTypeToMock
.
+ * @return
+ * @since 3.5.0
+ */
+ default Optional createSpy(
+ MockCreationSettings settings, MockHandler handler, T instance) {
+ return Optional.empty();
+ }
+
/**
* Returns the handler for the {@code mock}. Do not provide your own implementations at this time
* because the work on the {@link MockHandler} api is not completed.
@@ -141,6 +165,39 @@ default StaticMockControl createStaticMock(
"Note that Mockito's inline mock maker is not supported on Android."));
}
+ /**
+ * If you want to provide your own implementation of {@code MockMaker} this method should:
+ *
+ * Intercept all constructions of the specified type in the current thread
+ * Only intercept the construction after being enabled.
+ * Stops the interception when disabled.
+ *
+ *
+ * @param settingsFactory Factory for mock creation settings like type to mock, extra interfaces and so on.
+ * @param handlerFactory Factory for settings. See {@link org.mockito.invocation.MockHandler}.
+ * Do not provide your own implementation at this time. Make sure your implementation of
+ * {@link #getHandler(Object)} will return this instance.
+ * @param Type of the mock to return, actually the settings.getTypeToMock
.
+ * @return A control for the mocked construction.
+ * @since 3.5.0
+ */
+ @Incubating
+ default ConstructionMockControl createConstructionMock(
+ Class type,
+ Function> settingsFactory,
+ Function> handlerFactory,
+ MockedConstruction.MockInitializer mockInitializer) {
+ throw new MockitoException(
+ join(
+ "The used MockMaker "
+ + getClass().getSimpleName()
+ + " does not support the creation of construction mocks",
+ "",
+ "Mockito's inline mock maker supports construction mocks based on the Instrumentation API.",
+ "You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'.",
+ "Note that Mockito's inline mock maker is not supported on Android."));
+ }
+
/**
* Carries the mockability information
*
@@ -168,4 +225,16 @@ interface StaticMockControl {
void disable();
}
+
+ @Incubating
+ interface ConstructionMockControl {
+
+ Class getType();
+
+ void enable();
+
+ void disable();
+
+ List getMocks();
+ }
}
diff --git a/src/test/java/org/mockito/MockitoTest.java b/src/test/java/org/mockito/MockitoTest.java
index 23df60e08e..b41dd2091b 100644
--- a/src/test/java/org/mockito/MockitoTest.java
+++ b/src/test/java/org/mockito/MockitoTest.java
@@ -71,6 +71,12 @@ public void shouldGiveExplantionOnStaticMockingWithoutInlineMockMaker() {
Mockito.mockStatic(Object.class);
}
+ @SuppressWarnings({"CheckReturnValue", "MockitoUsage"})
+ @Test(expected = MockitoException.class)
+ public void shouldGiveExplantionOnConstructionMockingWithoutInlineMockMaker() {
+ Mockito.mockConstruction(Object.class);
+ }
+
@Test
public void shouldStartingMockSettingsContainDefaultBehavior() {
// when
diff --git a/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java b/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java
index 906d134d61..9c4ef88d88 100644
--- a/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java
+++ b/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java
@@ -26,24 +26,28 @@ public class MockAnnotationProcessorTest {
@Test
public void testNonGeneric() throws Exception {
Class> type =
- MockAnnotationProcessor.inferStaticMock(
+ MockAnnotationProcessor.inferParameterizedType(
MockAnnotationProcessorTest.class
.getDeclaredField("nonGeneric")
.getGenericType(),
- "nonGeneric");
+ "nonGeneric",
+ "Sample");
assertThat(type).isEqualTo(Void.class);
}
@Test(expected = MockitoException.class)
public void testGeneric() throws Exception {
- MockAnnotationProcessor.inferStaticMock(
+ MockAnnotationProcessor.inferParameterizedType(
MockAnnotationProcessorTest.class.getDeclaredField("generic").getGenericType(),
- "generic");
+ "generic",
+ "Sample");
}
@Test(expected = MockitoException.class)
public void testRaw() throws Exception {
- MockAnnotationProcessor.inferStaticMock(
- MockAnnotationProcessorTest.class.getDeclaredField("raw").getGenericType(), "raw");
+ MockAnnotationProcessor.inferParameterizedType(
+ MockAnnotationProcessorTest.class.getDeclaredField("raw").getGenericType(),
+ "raw",
+ "Sample");
}
}
diff --git a/src/test/java/org/mockito/internal/creation/bytebuddy/AbstractByteBuddyMockMakerTest.java b/src/test/java/org/mockito/internal/creation/bytebuddy/AbstractByteBuddyMockMakerTest.java
index f5b807a1ce..3ddc1817ba 100644
--- a/src/test/java/org/mockito/internal/creation/bytebuddy/AbstractByteBuddyMockMakerTest.java
+++ b/src/test/java/org/mockito/internal/creation/bytebuddy/AbstractByteBuddyMockMakerTest.java
@@ -165,7 +165,7 @@ public void instantiate_fine_when_objenesis_on_the_classpath() throws Exception
classpath_with_objenesis);
// when
- mock_maker_class_loaded_fine_until.newInstance();
+ mock_maker_class_loaded_fine_until.getConstructor().newInstance();
// then everything went fine
}
diff --git a/src/test/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMakerTest.java b/src/test/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMakerTest.java
index 8ec5318748..bfc16b4d15 100644
--- a/src/test/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMakerTest.java
+++ b/src/test/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMakerTest.java
@@ -11,11 +11,7 @@
import static org.assertj.core.api.Assertions.fail;
import static org.junit.Assume.assumeTrue;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
@@ -57,6 +53,39 @@ public void should_create_mock_from_final_class() throws Exception {
assertThat(proxy.foo()).isEqualTo("bar");
}
+ @Test
+ public void should_create_mock_from_final_spy() throws Exception {
+ MockCreationSettings settings = settingsFor(FinalSpy.class);
+ Optional proxy =
+ mockMaker.createSpy(
+ settings,
+ new MockHandlerImpl<>(settings),
+ new FinalSpy("value", true, (byte) 1, (short) 1, (char) 1, 1, 1L, 1f, 1d));
+ assertThat(proxy)
+ .hasValueSatisfying(
+ spy -> {
+ assertThat(spy.aString).isEqualTo("value");
+ assertThat(spy.aBoolean).isTrue();
+ assertThat(spy.aByte).isEqualTo((byte) 1);
+ assertThat(spy.aShort).isEqualTo((short) 1);
+ assertThat(spy.aChar).isEqualTo((char) 1);
+ assertThat(spy.anInt).isEqualTo(1);
+ assertThat(spy.aLong).isEqualTo(1L);
+ assertThat(spy.aFloat).isEqualTo(1f);
+ assertThat(spy.aDouble).isEqualTo(1d);
+ });
+ }
+
+ @Test
+ public void should_create_mock_from_non_constructable_class() throws Exception {
+ MockCreationSettings settings =
+ settingsFor(NonConstructableClass.class);
+ NonConstructableClass proxy =
+ mockMaker.createMock(
+ settings, new MockHandlerImpl(settings));
+ assertThat(proxy.foo()).isEqualTo("bar");
+ }
+
@Test
public void should_create_mock_from_final_class_in_the_JDK() throws Exception {
MockCreationSettings settings = settingsFor(Pattern.class);
@@ -406,6 +435,51 @@ public String foo() {
}
}
+ private static final class FinalSpy {
+
+ private final String aString;
+ private final boolean aBoolean;
+ private final byte aByte;
+ private final short aShort;
+ private final char aChar;
+ private final int anInt;
+ private final long aLong;
+ private final float aFloat;
+ private final double aDouble;
+
+ private FinalSpy(
+ String aString,
+ boolean aBoolean,
+ byte aByte,
+ short aShort,
+ char aChar,
+ int anInt,
+ long aLong,
+ float aFloat,
+ double aDouble) {
+ this.aString = aString;
+ this.aBoolean = aBoolean;
+ this.aByte = aByte;
+ this.aShort = aShort;
+ this.aChar = aChar;
+ this.anInt = anInt;
+ this.aLong = aLong;
+ this.aFloat = aFloat;
+ this.aDouble = aDouble;
+ }
+ }
+
+ private static class NonConstructableClass {
+
+ private NonConstructableClass() {
+ throw new AssertionError();
+ }
+
+ public String foo() {
+ return "foo";
+ }
+ }
+
private enum EnumClass {
INSTANCE;
diff --git a/src/test/java/org/mockito/internal/util/reflection/AccessibilityChangerTest.java b/src/test/java/org/mockito/internal/util/reflection/AccessibilityChangerTest.java
deleted file mode 100644
index 032cb53770..0000000000
--- a/src/test/java/org/mockito/internal/util/reflection/AccessibilityChangerTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2007 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-package org.mockito.internal.util.reflection;
-
-import static org.mockitoutil.VmArgAssumptions.assumeVmArgPresent;
-
-import java.lang.reflect.Field;
-import java.util.Observable;
-
-import org.junit.Test;
-
-public class AccessibilityChangerTest {
-
- @SuppressWarnings("unused")
- private Observable whatever;
-
- @Test
- public void should_enable_and_safely_disable() throws Exception {
- AccessibilityChanger changer = new AccessibilityChanger();
- changer.enableAccess(field("whatever"));
- changer.safelyDisableAccess(field("whatever"));
- }
-
- @Test(expected = java.lang.AssertionError.class)
- public void safelyDisableAccess_should_fail_when_enableAccess_not_called() throws Exception {
- assumeVmArgPresent("-ea");
- new AccessibilityChanger().safelyDisableAccess(field("whatever"));
- }
-
- private Field field(String fieldName) throws NoSuchFieldException {
- return this.getClass().getDeclaredField(fieldName);
- }
-}
diff --git a/src/test/java/org/mockito/internal/util/reflection/LenientCopyToolTest.java b/src/test/java/org/mockito/internal/util/reflection/LenientCopyToolTest.java
index 76ebd337b9..e70d8aa3c4 100644
--- a/src/test/java/org/mockito/internal/util/reflection/LenientCopyToolTest.java
+++ b/src/test/java/org/mockito/internal/util/reflection/LenientCopyToolTest.java
@@ -6,13 +6,13 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.*;
import java.lang.reflect.Field;
import java.util.LinkedList;
import org.junit.Test;
+import org.mockito.plugins.MemberAccessor;
import org.mockitoutil.TestBase;
@SuppressWarnings("unchecked")
@@ -127,36 +127,23 @@ public void shouldCopyValuesOfInheritedFields() throws Exception {
assertEquals(((InheritMe) from).privateInherited, ((InheritMe) to).privateInherited);
}
- @Test
- public void shouldEnableAndThenDisableAccessibility() throws Exception {
- // given
- Field privateField = SomeObject.class.getDeclaredField("privateField");
- assertFalse(privateField.isAccessible());
-
- // when
- tool.copyToMock(from, to);
-
- // then
- privateField = SomeObject.class.getDeclaredField("privateField");
- assertFalse(privateField.isAccessible());
- }
-
@Test
public void shouldContinueEvenIfThereAreProblemsCopyingSingleFieldValue() throws Exception {
// given
- tool.fieldCopier = mock(FieldCopier.class);
+ tool.accessor = mock(MemberAccessor.class);
doNothing()
- .doThrow(new IllegalAccessException())
+ .doThrow(new IllegalStateException())
.doNothing()
- .when(tool.fieldCopier)
- .copyValue(anyObject(), anyObject(), any(Field.class));
+ .when(tool.accessor)
+ .set(any(Field.class), anyObject(), anyObject());
// when
tool.copyToMock(from, to);
// then
- verify(tool.fieldCopier, atLeast(3)).copyValue(any(), any(), any(Field.class));
+ verify(tool.accessor, atLeast(3)).get(any(Field.class), any());
+ verify(tool.accessor, atLeast(3)).set(any(Field.class), any(), any());
}
@Test
diff --git a/src/test/java/org/mockito/internal/util/reflection/MemberAccessorTest.java b/src/test/java/org/mockito/internal/util/reflection/MemberAccessorTest.java
new file mode 100644
index 0000000000..ee390312ed
--- /dev/null
+++ b/src/test/java/org/mockito/internal/util/reflection/MemberAccessorTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.plugins.MemberAccessor;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+@RunWith(Parameterized.class)
+public class MemberAccessorTest {
+
+ @Parameterized.Parameters
+ public static Collection data() {
+ List data = new ArrayList<>();
+ data.add(new Object[] {new ReflectionMemberAccessor()});
+ data.add(new Object[] {new ModuleMemberAccessor()});
+ return data;
+ }
+
+ private final MemberAccessor accessor;
+
+ public MemberAccessorTest(MemberAccessor accessor) {
+ this.accessor = accessor;
+ }
+
+ @Test
+ public void test_read_field() throws Exception {
+ assertThat(accessor.get(Sample.class.getDeclaredField("field"), new Sample("foo")))
+ .isEqualTo("foo");
+ }
+
+ @Test
+ public void test_read_static_field() throws Exception {
+ Sample.staticField = "foo";
+ assertThat(accessor.get(Sample.class.getDeclaredField("staticField"), null))
+ .isEqualTo("foo");
+ }
+
+ @Test
+ public void test_write_field() throws Exception {
+ Sample sample = new Sample("foo");
+ accessor.set(Sample.class.getDeclaredField("field"), sample, "bar");
+ assertThat(sample.field).isEqualTo("bar");
+ }
+
+ @Test
+ public void test_write_static_field() throws Exception {
+ Sample.staticField = "foo";
+ accessor.set(Sample.class.getDeclaredField("staticField"), null, "bar");
+ assertThat(Sample.staticField).isEqualTo("bar");
+ }
+
+ @Test
+ public void test_invoke() throws Exception {
+ assertThat(
+ accessor.invoke(
+ Sample.class.getDeclaredMethod("test", String.class),
+ new Sample(null),
+ "foo"))
+ .isEqualTo("foo");
+ }
+
+ @Test
+ public void test_invoke_invocation_exception() {
+ assertThatThrownBy(
+ () ->
+ accessor.invoke(
+ Sample.class.getDeclaredMethod("test", String.class),
+ new Sample(null),
+ "exception"))
+ .isInstanceOf(InvocationTargetException.class);
+ }
+
+ @Test
+ public void test_invoke_illegal_arguments() {
+ assertThatThrownBy(
+ () ->
+ accessor.invoke(
+ Sample.class.getDeclaredMethod("test", String.class),
+ new Sample(null),
+ 42))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void test_new_instance() throws Exception {
+ assertThat(accessor.newInstance(Sample.class.getDeclaredConstructor(String.class), "foo"))
+ .isInstanceOf(Sample.class);
+ }
+
+ @Test
+ public void test_new_instance_illegal_arguments() {
+ assertThatThrownBy(
+ () ->
+ accessor.newInstance(
+ Sample.class.getDeclaredConstructor(String.class), 42))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void test_new_instance_invocation_exception() {
+ assertThatThrownBy(
+ () ->
+ accessor.newInstance(
+ Sample.class.getDeclaredConstructor(String.class),
+ "exception"))
+ .isInstanceOf(InvocationTargetException.class);
+ }
+
+ @Test
+ public void test_new_instance_instantiation_exception() {
+ assertThatThrownBy(
+ () -> accessor.newInstance(AbstractSample.class.getDeclaredConstructor()))
+ .isInstanceOf(InstantiationException.class);
+ }
+
+ @Test
+ public void test_set_final_field() throws Exception {
+ Sample sample = new Sample("foo");
+ accessor.set(Sample.class.getDeclaredField("finalField"), sample, "foo");
+ assertThat(sample.finalField).isEqualTo("foo");
+ }
+
+ private static class Sample {
+
+ private String field;
+
+ private final String finalField = null;
+
+ private static String staticField = "foo";
+
+ public Sample(String field) {
+ if ("exception".equals(field)) {
+ throw new RuntimeException();
+ }
+ this.field = field;
+ }
+
+ private String test(String value) {
+ if ("exception".equals(value)) {
+ throw new RuntimeException();
+ }
+ return value;
+ }
+ }
+
+ private abstract static class AbstractSample {}
+}
diff --git a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java
index 342007122c..2363fe6f35 100644
--- a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java
+++ b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java
@@ -41,6 +41,10 @@ public void pure_mockito_should_not_depend_bytecode_libraries() throws Exception
pureMockitoAPIClasses.remove("org.mockito.internal.exceptions.stacktrace.StackTraceFilter");
pureMockitoAPIClasses.remove("org.mockito.internal.util.MockUtil");
+ // Remove instrumentation-based member accessor which is optional.
+ pureMockitoAPIClasses.remove(
+ "org.mockito.internal.util.reflection.InstrumentationMemberAccessor");
+
for (String pureMockitoAPIClass : pureMockitoAPIClasses) {
checkDependency(classLoader_without_bytecode_libraries, pureMockitoAPIClass);
}
diff --git a/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java b/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java
index 765c4b8d0a..0d2af98d67 100644
--- a/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java
+++ b/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java
@@ -12,6 +12,7 @@
import java.util.TreeSet;
import org.assertj.core.api.Assertions;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
@@ -50,10 +51,19 @@ public class MockInjectionUsingSetterOrPropertyTest extends TestBase {
@Spy private TreeSet searchTree = new TreeSet();
+ private AutoCloseable session;
+
@Before
public void enforces_new_instances() {
- // initMocks called in TestBase Before method, so instances are not the same
- MockitoAnnotations.openMocks(this);
+ // openMocks called in TestBase Before method, so instances are not the same
+ session = MockitoAnnotations.openMocks(this);
+ }
+
+ @After
+ public void close_new_instances() throws Exception {
+ if (session != null) {
+ session.close();
+ }
}
@Test
diff --git a/src/test/java/org/mockitousage/spies/SpyingOnInterfacesTest.java b/src/test/java/org/mockitousage/spies/SpyingOnInterfacesTest.java
index ec8d5933d2..b6fe514cde 100644
--- a/src/test/java/org/mockitousage/spies/SpyingOnInterfacesTest.java
+++ b/src/test/java/org/mockitousage/spies/SpyingOnInterfacesTest.java
@@ -106,7 +106,7 @@ public void shouldAllowSpyingOnDefaultMethod() throws Exception {
.load(iFace.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
- Object object = spy(impl.newInstance());
+ Object object = spy(impl.getConstructor().newInstance());
// when
Assertions.assertThat(impl.getMethod("foo").invoke(object)).isEqualTo((Object) "bar");
diff --git a/src/test/java/org/mockitoutil/ClassLoaders.java b/src/test/java/org/mockitoutil/ClassLoaders.java
index 2b3f51051d..308b309ec8 100644
--- a/src/test/java/org/mockitoutil/ClassLoaders.java
+++ b/src/test/java/org/mockitoutil/ClassLoaders.java
@@ -36,6 +36,8 @@
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.plugins.MemberAccessor;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.objenesis.instantiator.ObjectInstantiator;
@@ -167,9 +169,8 @@ Runnable reloadTaskInClassLoader(Runnable task) {
continue;
}
if (declaredField.getType() == field.getType()) { // don't copy this
- field.setAccessible(true);
- declaredField.setAccessible(true);
- declaredField.set(reloaded, field.get(task));
+ MemberAccessor accessor = Plugins.getMemberAccessor();
+ accessor.set(declaredField, reloaded, accessor.get(field, task));
}
}
diff --git a/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor b/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor
new file mode 100644
index 0000000000..1422f9900b
--- /dev/null
+++ b/subprojects/inline/src/main/resources/mockito-extensions/org.mockito.plugins.MemberAccessor
@@ -0,0 +1 @@
+member-accessor-module
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java
new file mode 100644
index 0000000000..e53202eb33
--- /dev/null
+++ b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockRuleTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitoinline;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static junit.framework.TestCase.*;
+
+public final class ConstructionMockRuleTest {
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private MockedConstruction dummy;
+
+ @Test
+ public void testConstructionMockSimple() {
+ assertNull(new Dummy().foo());
+ }
+
+ @Test
+ public void testConstructionMockCollection() {
+ assertEquals(0, dummy.constructed().size());
+ Dummy mock = new Dummy();
+ assertEquals(1, dummy.constructed().size());
+ assertTrue(dummy.constructed().contains(mock));
+ }
+
+ static class Dummy {
+
+ String foo() {
+ return "foo";
+ }
+ }
+}
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java
new file mode 100644
index 0000000000..dd5e46d494
--- /dev/null
+++ b/subprojects/inline/src/test/java/org/mockitoinline/ConstructionMockTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitoinline;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.exceptions.base.MockitoException;
+
+import static junit.framework.TestCase.*;
+import static org.mockito.Mockito.*;
+
+public final class ConstructionMockTest {
+
+ @Test
+ public void testConstructionMockSimple() {
+ assertEquals("foo", new Dummy().foo());
+ try (MockedConstruction ignored = Mockito.mockConstruction(Dummy.class)) {
+ assertNull(new Dummy().foo());
+ }
+ assertEquals("foo", new Dummy().foo());
+ }
+
+ @Test
+ public void testConstructionMockCollection() {
+ try (MockedConstruction dummy = Mockito.mockConstruction(Dummy.class)) {
+ assertEquals(0, dummy.constructed().size());
+ Dummy mock = new Dummy();
+ assertEquals(1, dummy.constructed().size());
+ assertTrue(dummy.constructed().contains(mock));
+ }
+ }
+
+ @Test
+ public void testConstructionMockDefaultAnswer() {
+ try (MockedConstruction ignored = Mockito.mockConstructionWithAnswer(Dummy.class, invocation -> "bar")) {
+ assertEquals("bar", new Dummy().foo());
+ }
+ }
+
+ @Test
+ public void testConstructionMockDefaultAnswerMultiple() {
+ try (MockedConstruction ignored = Mockito.mockConstructionWithAnswer(Dummy.class, invocation -> "bar", invocation -> "qux")) {
+ assertEquals("bar", new Dummy().foo());
+ assertEquals("qux", new Dummy().foo());
+ assertEquals("qux", new Dummy().foo());
+ }
+ }
+
+ @Test
+ public void testConstructionMockPrepared() {
+ try (MockedConstruction ignored = Mockito.mockConstruction(Dummy.class, (mock, context) -> when(mock.foo()).thenReturn("bar"))) {
+ assertEquals("bar", new Dummy().foo());
+ }
+ }
+
+
+ @Test
+ public void testConstructionMockContext() {
+ try (MockedConstruction ignored = Mockito.mockConstruction(Dummy.class, (mock, context) -> {
+ assertEquals(1, context.getCount());
+ assertEquals(Collections.singletonList("foobar"), context.arguments());
+ assertEquals(mock.getClass().getDeclaredConstructor(String.class), context.constructor());
+ when(mock.foo()).thenReturn("bar");
+ })) {
+ assertEquals("bar", new Dummy("foobar").foo());
+ }
+ }
+
+ @Test
+ public void testConstructionMockDoesNotAffectDifferentThread() throws InterruptedException {
+ try (MockedConstruction ignored = Mockito.mockConstruction(Dummy.class)) {
+ Dummy dummy = new Dummy();
+ when(dummy.foo()).thenReturn("bar");
+ assertEquals("bar", dummy.foo());
+ verify(dummy).foo();
+ AtomicReference reference = new AtomicReference<>();
+ Thread thread = new Thread(() -> reference.set(new Dummy().foo()));
+ thread.start();
+ thread.join();
+ assertEquals("foo", reference.get());
+ when(dummy.foo()).thenReturn("bar");
+ assertEquals("bar", dummy.foo());
+ verify(dummy, times(2)).foo();
+ }
+ }
+
+ @Test
+ public void testConstructionMockCanCoexistWithMockInDifferentThread() throws InterruptedException {
+ try (MockedConstruction ignored = Mockito.mockConstruction(Dummy.class)) {
+ Dummy dummy = new Dummy();
+ when(dummy.foo()).thenReturn("bar");
+ assertEquals("bar", dummy.foo());
+ verify(dummy).foo();
+ AtomicReference reference = new AtomicReference<>();
+ Thread thread = new Thread(() -> {
+ try (MockedConstruction ignored2 = Mockito.mockConstruction(Dummy.class)) {
+ Dummy other = new Dummy();
+ when(other.foo()).thenReturn("qux");
+ reference.set(other.foo());
+ }
+ });
+ thread.start();
+ thread.join();
+ assertEquals("qux", reference.get());
+ assertEquals("bar", dummy.foo());
+ verify(dummy, times(2)).foo();
+ }
+ }
+
+ @Test(expected = MockitoException.class)
+ public void testConstructionMockMustBeExclusiveInScopeWithinThread() {
+ try (
+ MockedConstruction dummy = Mockito.mockConstruction(Dummy.class);
+ MockedConstruction duplicate = Mockito.mockConstruction(Dummy.class)
+ ) {
+ fail("Not supposed to allow duplicates");
+ }
+ }
+
+ @Test(expected = MockitoException.class)
+ public void testConstructionMockMustNotTargetAbstractClass() {
+ Mockito.mockConstruction(Runnable.class).close();
+ }
+
+ static class Dummy {
+
+
+ public Dummy() {
+ }
+
+ public Dummy(String value) {
+ }
+
+ String foo() {
+ return "foo";
+ }
+ }
+}
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockRuleTest.java b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockRuleTest.java
new file mode 100644
index 0000000000..c54d6a6cf1
--- /dev/null
+++ b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockRuleTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitoinline;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static junit.framework.TestCase.*;
+
+public final class StaticMockRuleTest {
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private MockedStatic dummy;
+
+ @Test
+ public void testStaticMockSimple() {
+ assertNull(Dummy.foo());
+ }
+
+ @Test
+ public void testStaticMockWithVerification() {
+ dummy.when(Dummy::foo).thenReturn("bar");
+ assertEquals("bar", Dummy.foo());
+ dummy.verify(Dummy::foo);
+ }
+
+ static class Dummy {
+
+ static String foo() {
+ return "foo";
+ }
+ }
+}
diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java
index 57115fa979..06c14d2bb3 100644
--- a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java
+++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java
@@ -4,10 +4,9 @@
*/
package org.mockitousage;
-import java.util.UUID;
-
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
+import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import static org.assertj.core.api.Assertions.*;
@@ -15,11 +14,29 @@
class NoExtendsTest {
@Mock
- private MockedStatic mock;
+ private MockedStatic staticMethod;
+
+ @Mock
+ private MockedConstruction construction;
+
+ @Test
+ void runsStaticMethods() {
+ assertThat(Dummy.foo()).isNull();
+ }
@Test
- void runs() {
- mock.when(UUID::randomUUID).thenReturn(new UUID(123, 456));
- assertThat(UUID.randomUUID()).isEqualTo(new UUID(123, 456));
+ void runsConstruction() {
+ assertThat(new Dummy().bar()).isNull();
+ }
+
+ static class Dummy {
+
+ static String foo() {
+ return "foo";
+ }
+
+ String bar() {
+ return "foo";
+ }
}
}
diff --git a/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleAccessTest.java b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleAccessTest.java
new file mode 100644
index 0000000000..6382a253b8
--- /dev/null
+++ b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleAccessTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.moduletest;
+
+import org.junit.Test;
+import org.mockito.internal.util.reflection.ModuleMemberAccessor;
+import org.mockito.internal.util.reflection.ReflectionMemberAccessor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Path;
+import java.util.concurrent.Callable;
+
+import static junit.framework.TestCase.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.moduletest.ModuleUtil.layer;
+import static org.mockito.moduletest.ModuleUtil.modularJar;
+
+public class ModuleAccessTest {
+
+ @Test
+ public void can_access_non_opened_module_with_module_member_accessor() throws Exception {
+ Path jar = modularJar(false, false, false);
+ ModuleLayer layer = layer(jar, false, true);
+
+ ClassLoader loader = layer.findLoader("mockito.test");
+ Class> type = loader.loadClass("sample.MyCallable");
+
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader);
+ try {
+ Class> moduleMemberAccessor = loader.loadClass(ModuleMemberAccessor.class.getName());
+ Object instance = moduleMemberAccessor.getConstructor().newInstance();
+ @SuppressWarnings("unchecked")
+ Callable mock = (Callable) moduleMemberAccessor
+ .getMethod("newInstance", Constructor.class, Object[].class)
+ .invoke(instance, type.getConstructor(), new Object[0]);
+ assertThat(mock.call()).isEqualTo("foo");
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+
+ @Test
+ public void cannot_access_non_opened_module_with_reflection_member_accessor() throws Exception {
+ Path jar = modularJar(false, false, false);
+ ModuleLayer layer = layer(jar, false, true);
+
+ ClassLoader loader = layer.findLoader("mockito.test");
+ Class> type = loader.loadClass("sample.MyCallable");
+
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader);
+ try {
+ Class> moduleMemberAccessor = loader.loadClass(ReflectionMemberAccessor.class.getName());
+ try {
+ Object instance = moduleMemberAccessor.getConstructor().newInstance();
+ moduleMemberAccessor
+ .getMethod("newInstance", Constructor.class, Object[].class)
+ .invoke(instance, type.getConstructor(), new Object[0]);
+ fail();
+ } catch (InvocationTargetException e) {
+ assertThat(e.getCause()).isInstanceOf(IllegalAccessException.class);
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+}
diff --git a/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java
index ed732c8089..fec5e9a841 100644
--- a/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java
+++ b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java
@@ -4,15 +4,7 @@
*/
package org.mockito.moduletest;
-import java.util.concurrent.locks.Lock;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassInjector;
-import net.bytebuddy.implementation.FixedValue;
-import net.bytebuddy.jar.asm.ClassWriter;
-import net.bytebuddy.jar.asm.ModuleVisitor;
-import net.bytebuddy.jar.asm.Opcodes;
-import net.bytebuddy.utility.OpenedClassReader;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -22,33 +14,19 @@
import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker;
import org.mockito.stubbing.OngoingStubbing;
-import java.io.IOException;
-import java.lang.module.Configuration;
-import java.lang.module.ModuleDescriptor;
-import java.lang.module.ModuleFinder;
-import java.lang.module.ModuleReader;
-import java.lang.module.ModuleReference;
import java.lang.reflect.InvocationTargetException;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.Callable;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.stream.Stream;
+import java.util.concurrent.locks.Lock;
-import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.hamcrest.core.Is.is;
import static org.junit.Assume.assumeThat;
+import static org.mockito.moduletest.ModuleUtil.layer;
+import static org.mockito.moduletest.ModuleUtil.modularJar;
@RunWith(Parameterized.class)
public class ModuleHandlingTest {
@@ -71,7 +49,7 @@ public void can_define_class_in_open_reading_module() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(true, true, true);
- ModuleLayer layer = layer(jar, true);
+ ModuleLayer layer = layer(jar, true, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -97,7 +75,7 @@ public void can_define_class_in_open_java_util_module() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(true, true, true);
- ModuleLayer layer = layer(jar, true);
+ ModuleLayer layer = layer(jar, true, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("java.util.concurrent.locks.Lock");
@@ -125,7 +103,7 @@ public void inline_mock_maker_can_mock_closed_modules() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(true));
Path jar = modularJar(false, false, false);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -145,7 +123,7 @@ public void can_define_class_in_open_reading_private_module() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(false, true, true);
- ModuleLayer layer = layer(jar, true);
+ ModuleLayer layer = layer(jar, true, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -171,7 +149,7 @@ public void can_define_class_in_open_non_reading_module() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(true, true, true);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -197,7 +175,7 @@ public void can_define_class_in_open_non_reading_non_exporting_module() throws E
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(true, false, true);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -223,7 +201,7 @@ public void can_define_class_in_closed_module() throws Exception {
assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false));
Path jar = modularJar(true, true, false);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -252,7 +230,7 @@ public void cannot_define_class_in_non_opened_non_exported_module_if_lookup_inje
assumeThat(!Boolean.getBoolean("org.mockito.internal.noUnsafeInjection") && ClassInjector.UsingReflection.isAvailable(), is(true));
Path jar = modularJar(false, false, false);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -279,7 +257,7 @@ public void can_define_class_in_non_opened_non_exported_module_if_unsafe_injecti
assumeThat(!Boolean.getBoolean("org.mockito.internal.noUnsafeInjection") && ClassInjector.UsingReflection.isAvailable(), is(false));
Path jar = modularJar(false, false, false);
- ModuleLayer layer = layer(jar, false);
+ ModuleLayer layer = layer(jar, false, namedModules);
ClassLoader loader = layer.findLoader("mockito.test");
Class> type = loader.loadClass("sample.MyCallable");
@@ -298,120 +276,4 @@ public void can_define_class_in_non_opened_non_exported_module_if_unsafe_injecti
Thread.currentThread().setContextClassLoader(contextLoader);
}
}
-
- private static Path modularJar(boolean isPublic, boolean isExported, boolean isOpened) throws IOException {
- Path jar = Files.createTempFile("sample-module", ".jar");
- try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar))) {
- out.putNextEntry(new JarEntry("module-info.class"));
- out.write(moduleInfo(isExported, isOpened));
- out.closeEntry();
- out.putNextEntry(new JarEntry("sample/MyCallable.class"));
- out.write(type(isPublic));
- out.closeEntry();
- }
- return jar;
- }
-
- private static byte[] type(boolean isPublic) {
- return new ByteBuddy()
- .subclass(Callable.class)
- .name("sample.MyCallable")
- .merge(isPublic ? Visibility.PUBLIC : Visibility.PACKAGE_PRIVATE)
- .method(named("call"))
- .intercept(FixedValue.value("foo"))
- .make()
- .getBytes();
- }
-
- private static byte[] moduleInfo(boolean isExported, boolean isOpened) {
- ClassWriter classWriter = new ClassWriter(OpenedClassReader.ASM_API);
- classWriter.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null);
- ModuleVisitor mv = classWriter.visitModule("mockito.test", 0, null);
- mv.visitRequire("java.base", Opcodes.ACC_MANDATED, null);
- mv.visitPackage("sample");
- if (isExported) {
- mv.visitExport("sample", 0);
- }
- if (isOpened) {
- mv.visitOpen("sample", 0);
- }
- mv.visitEnd();
- classWriter.visitEnd();
- return classWriter.toByteArray();
- }
-
- private ModuleLayer layer(Path jar, boolean canRead) throws MalformedURLException {
- Set modules = new HashSet<>();
- modules.add("mockito.test");
- ModuleFinder moduleFinder = ModuleFinder.of(jar);
- if (namedModules) {
- modules.add("org.mockito");
- modules.add("net.bytebuddy");
- modules.add("net.bytebuddy.agent");
- // We do not list all packages but only roots and packages that interact with the mock where
- // we attempt to validate an interaction of two modules. This is of course a bit hacky as those
- // libraries would normally be entirely encapsulated in an automatic module with all their classes
- // but it is sufficient for the test and saves us a significant amount of code.
- moduleFinder = ModuleFinder.compose(moduleFinder,
- automaticModule("org.mockito", "org.mockito", "org.mockito.internal.creation.bytebuddy"),
- automaticModule("net.bytebuddy", "net.bytebuddy"),
- automaticModule("net.bytebuddy.agent", "net.bytebuddy.agent"));
- }
- Configuration configuration = Configuration.resolve(
- moduleFinder,
- Collections.singletonList(ModuleLayer.boot().configuration()),
- ModuleFinder.of(),
- modules
- );
- ClassLoader classLoader = new ReplicatingClassLoader(jar);
- ModuleLayer.Controller controller = ModuleLayer.defineModules(
- configuration,
- Collections.singletonList(ModuleLayer.boot()),
- module -> classLoader
- );
- if (canRead) {
- controller.addReads(
- controller.layer().findModule("mockito.test").orElseThrow(IllegalStateException::new),
- Mockito.class.getModule()
- );
- }
- return controller.layer();
- }
-
- private static ModuleFinder automaticModule(String moduleName, String... packages) {
- ModuleDescriptor descriptor = ModuleDescriptor.newAutomaticModule(moduleName)
- .packages(new HashSet<>(Arrays.asList(packages)))
- .build();
- ModuleReference reference = new ModuleReference(descriptor, null) {
- @Override
- public ModuleReader open() {
- return new ModuleReader() {
- @Override
- public Optional