diff --git a/src/main/java/org/mockito/Captor.java b/src/main/java/org/mockito/Captor.java index 225bf700f2..358f37ef0a 100644 --- a/src/main/java/org/mockito/Captor.java +++ b/src/main/java/org/mockito/Captor.java @@ -15,9 +15,16 @@ * * @Captor ArgumentCaptor<AsyncCallback<Foo>> captor; * + * private AutoCloseable closeable; + * * @Before - * public void init(){ - * MockitoAnnotations.initMocks(this); + * public void open() { + * MockitoAnnotations.openMocks(this); + * } + * + * @After + * public void release() throws Exception { + * closeable.close(); * } * * @Test public void shouldDoSomethingUseful() { diff --git a/src/main/java/org/mockito/InjectMocks.java b/src/main/java/org/mockito/InjectMocks.java index 375c35514d..3327f9dc33 100644 --- a/src/main/java/org/mockito/InjectMocks.java +++ b/src/main/java/org/mockito/InjectMocks.java @@ -71,8 +71,14 @@ * * public class SampleBaseTestCase { * - * @Before public void initMocks() { - * MockitoAnnotations.initMocks(this); + * private AutoCloseable closeable; + * + * @Before public void openMocks() { + * closeable = MockitoAnnotations.openMocks(this); + * } + * + * @After public void releaseMocks() throws Exception { + * closeable.close(); * } * } * @@ -141,11 +147,11 @@ *

* *

- * MockitoAnnotations.initMocks(this) method has to be called to initialize annotated objects. - * In above example, initMocks() is called in @Before (JUnit4) method of test's base class. - * For JUnit3 initMocks() can go to setup() method of a base class. - * Instead you can also put initMocks() in your JUnit runner (@RunWith) or use the built-in - * {@link MockitoJUnitRunner}. + * MockitoAnnotations.openMocks(this) method has to be called to initialize annotated objects. + * In above example, openMocks() is called in @Before (JUnit4) method of test's base class. + * For JUnit3 openMocks() can go to setup() method of a base class. + * Instead you can also put openMocks() in your JUnit runner (@RunWith) or use the built-in + * {@link MockitoJUnitRunner}. Also, make sure to release any mocks after disposing your test class with a corresponding hook. *

* *

@@ -155,7 +161,7 @@ * * @see Mock * @see Spy - * @see MockitoAnnotations#initMocks(Object) + * @see MockitoAnnotations#openMocks(Object) * @see MockitoJUnitRunner * @since 1.8.3 */ diff --git a/src/main/java/org/mockito/Mock.java b/src/main/java/org/mockito/Mock.java index 2473557e56..cb5bfba157 100644 --- a/src/main/java/org/mockito/Mock.java +++ b/src/main/java/org/mockito/Mock.java @@ -23,6 +23,7 @@ *

  • Minimizes repetitive mock creation code.
  • *
  • Makes the test class more readable.
  • *
  • Makes the verification error easier to read because the field name is used to identify the mock.
  • + *
  • Automatically detects static mocks of type {@link MockedStatic} and infers the static mock type of the type parameter.
  • * * *
    
    @@ -43,24 +44,30 @@
      *
      *   public class SampleBaseTestCase {
      *
    - *       @Before public void initMocks() {
    - *           MockitoAnnotations.initMocks(this);
    + *       private AutoCloseable closeable;
    + *
    + *       @Before public void openMocks() {
    + *           closeable = MockitoAnnotations.openMocks(this);
    + *       }
    + *
    + *       @After public void releaseMocks() throws Exception {
    + *           closeable.close();
      *       }
      *   }
      * 
    * *

    - * MockitoAnnotations.initMocks(this) method has to be called to initialize annotated objects. - * In above example, initMocks() is called in @Before (JUnit4) method of test's base class. - * For JUnit3 initMocks() can go to setup() method of a base class. - * Instead you can also put initMocks() in your JUnit runner (@RunWith) or use the built-in - * {@link MockitoJUnitRunner}. + * MockitoAnnotations.openMocks(this) method has to be called to initialize annotated objects. + * In above example, openMocks() is called in @Before (JUnit4) method of test's base class. + * For JUnit3 openMocks() can go to setup() method of a base class. + * Instead you can also put openMocks() in your JUnit runner (@RunWith) or use the built-in + * {@link MockitoJUnitRunner}. Also, make sure to release any mocks after disposing your test class with a corresponding hook. *

    * * @see Mockito#mock(Class) * @see Spy * @see InjectMocks - * @see MockitoAnnotations#initMocks(Object) + * @see MockitoAnnotations#openMocks(Object) * @see MockitoJUnitRunner */ @Target({FIELD, PARAMETER}) diff --git a/src/main/java/org/mockito/MockSettings.java b/src/main/java/org/mockito/MockSettings.java index 30496d164c..a1fdb508fd 100644 --- a/src/main/java/org/mockito/MockSettings.java +++ b/src/main/java/org/mockito/MockSettings.java @@ -340,6 +340,21 @@ public interface MockSettings extends Serializable { @Incubating MockCreationSettings build(Class typeToMock); + /** + * Creates immutable view of mock settings used later by Mockito, for use within a static mocking. + * Framework integrators can use this method to create instances of creation settings + * and use them in advanced use cases, for example to create invocations with {@link InvocationFactory}, + * or to implement custom {@link MockHandler}. + * Since {@link MockCreationSettings} is {@link NotExtensible}, Mockito public API needs a creation method for this type. + * + * @param classToMock class to mock + * @param type to mock + * @return immutable view of mock settings + * @since 2.10.0 + */ + @Incubating + MockCreationSettings buildStatic(Class classToMock); + /** * Lenient mocks bypass "strict stubbing" validation (see {@link Strictness#STRICT_STUBS}). * When mock is declared as lenient none of its stubbings will be checked for potential stubbing problems such as diff --git a/src/main/java/org/mockito/MockedStatic.java b/src/main/java/org/mockito/MockedStatic.java new file mode 100644 index 0000000000..b08f36f156 --- /dev/null +++ b/src/main/java/org/mockito/MockedStatic.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito; + +import org.mockito.stubbing.OngoingStubbing; +import org.mockito.verification.VerificationMode; + +import static org.mockito.Mockito.*; + +/** + * 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 MockedStatic#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 interface MockedStatic extends AutoCloseable { + + /** + * See {@link Mockito#when(Object)}. + */ + OngoingStubbing when(Verification verification); + + /** + * See {@link Mockito#verify(Object)}. + */ + default void verify(Verification verification) { + verify(times(1), verification); + } + + /** + * See {@link Mockito#verify(Object, VerificationMode)}. + */ + void verify(VerificationMode mode, Verification verification); + + /** + * See {@link Mockito#reset(Object[])}. + */ + void reset(); + + /** + * See {@link Mockito#clearInvocations(Object[])}. + */ + void clearInvocations(); + + /** + * {@link Mockito#verifyNoMoreInteractions(Object...)}. + */ + void verifyNoMoreInteractions(); + + /** + * See {@link Mockito#verifyNoInteractions(Object...)}. + */ + void verifyNoInteractions(); + + @Override + void close(); + + /** + * Releases this static mock and is non-operational if already released. + */ + void closeOnDemand(); + + @FunctionalInterface + 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 07a5dfcb56..602eb1cfb8 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -105,6 +105,7 @@ * 45. New JUnit Jupiter (JUnit5+) extension
    * 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)
    * * *

    0. Migrating to Mockito 2

    @@ -461,7 +462,7 @@ * runner: * *
    
    - * MockitoAnnotations.initMocks(testClass);
    + * MockitoAnnotations.openMocks(testClass);
      * 
    * * You can use built-in runner: {@link MockitoJUnitRunner} or a rule: {@link MockitoRule}. @@ -863,7 +864,7 @@ * should only use partial mocks as a last resort. See point 16 about partial mocks. * *

    - * All new annotations are *only* processed on {@link MockitoAnnotations#initMocks(Object)}. + * All new annotations are *only* processed on {@link MockitoAnnotations#openMocks(Object)}. * Just like for @{@link Mock} annotation you can use the built-in runner: {@link MockitoJUnitRunner} or rule: * {@link MockitoRule}. *

    @@ -907,7 +908,7 @@ * Mockito will now try to instantiate @{@link Spy} and will instantiate @{@link InjectMocks} fields * using constructor injection, setter injection, or field injection. *

    - * To take advantage of this feature you need to use {@link MockitoAnnotations#initMocks(Object)}, {@link MockitoJUnitRunner} + * To take advantage of this feature you need to use {@link MockitoAnnotations#openMocks(Object)}, {@link MockitoJUnitRunner} * or {@link MockitoRule}. *

    * Read more about available tricks and the rules of injection in the javadoc for {@link InjectMocks} @@ -1144,7 +1145,7 @@ * *

      *
    • Annotating the JUnit test class with a @{@link org.junit.runner.RunWith}({@link MockitoJUnitRunner}.class)
    • - *
    • Invoking {@link MockitoAnnotations#initMocks(Object)} in the @{@link org.junit.Before} method
    • + *
    • Invoking {@link MockitoAnnotations#openMocks(Object)} in the @{@link org.junit.Before} method
    • *
    * * Now you can choose to use a rule : @@ -1541,6 +1542,29 @@ * Hence, we introduced a new API to explicitly clear mock state (only make sense in inline mocking!). * See example usage in {@link MockitoFramework#clearInlineMocks()}. * If you have feedback or a better idea how to solve the problem please reach out. + * + * + *

    48. Mocking static methods (since 3.4.0)

    + * + * When using the inline mock maker, it is possible to mock static method 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 static mock remains temporary, it is recommended to define the scope within a try-with-resources construct. + * In the following example, the Foo type's static method would return foo unless mocked: + * + *
    
    + * assertEquals("foo", Foo.method());
    + * try (MockedStatic mocked = mockStatic(Foo.class)) {
    + * mocked.when(Foo::method).thenReturn("bar");
    + * assertEquals("bar", Foo.method());
    + * mocked.verify(Foo::method);
    + * }
    + * 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 + * behavior and to verify static method invocations, use the MockedStatic that is returned. + *

    */ @SuppressWarnings("unchecked") public class Mockito extends ArgumentMatchers { @@ -2026,6 +2050,73 @@ public static T spy(Class classToSpy) { classToSpy, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS)); } + /** + * Creates a thread-local mock controller for all static methods of the given class or interface. + * The returned object's {@link MockedStatic#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 class or interface of which static mocks should be mocked. + * @return mock controller + */ + @Incubating + @CheckReturnValue + public static MockedStatic mockStatic(Class classToMock) { + return mockStatic(classToMock, withSettings()); + } + + /** + * Creates a thread-local mock controller for all static methods of the given class or interface. + * The returned object's {@link MockedStatic#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 class or interface of which static mocks should be mocked. + * @param defaultAnswer the default answer when invoking static methods. + * @return mock controller + */ + @Incubating + @CheckReturnValue + public static MockedStatic mockStatic(Class classToMock, Answer defaultAnswer) { + return mockStatic(classToMock, withSettings().defaultAnswer(defaultAnswer)); + } + + /** + * Creates a thread-local mock controller for all static methods of the given class or interface. + * The returned object's {@link MockedStatic#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 class or interface of which static mocks should be mocked. + * @param name the name of the mock to use in error messages. + * @return mock controller + */ + @Incubating + @CheckReturnValue + public static MockedStatic mockStatic(Class classToMock, String name) { + return mockStatic(classToMock, withSettings().name(name)); + } + + /** + * Creates a thread-local mock controller for all static methods of the given class or interface. + * The returned object's {@link MockedStatic#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 class or interface of which static mocks should be mocked. + * @param mockSettings the settings to use where only name and default answer are considered. + * @return mock controller + */ + @Incubating + @CheckReturnValue + public static MockedStatic mockStatic(Class classToMock, MockSettings mockSettings) { + return MOCKITO_CORE.mockStatic(classToMock, mockSettings); + } + /** * 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/MockitoAnnotations.java b/src/main/java/org/mockito/MockitoAnnotations.java index ba355e702f..b1067702c1 100644 --- a/src/main/java/org/mockito/MockitoAnnotations.java +++ b/src/main/java/org/mockito/MockitoAnnotations.java @@ -9,8 +9,10 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.plugins.AnnotationEngine; +import static org.mockito.internal.util.StringUtil.*; + /** - * MockitoAnnotations.initMocks(this); initializes fields annotated with Mockito annotations. + * MockitoAnnotations.openMocks(this); initializes fields annotated with Mockito annotations. * See also {@link MockitoSession} which not only initializes mocks * but also adds extra validation for cleaner tests! *

    @@ -37,19 +39,27 @@ * * public class SampleBaseTestCase { * - * @Before public void initMocks() { - * MockitoAnnotations.initMocks(this); + * private AutoCloseable closeable; + * + * @Before public void openMocks() { + * closeable = MockitoAnnotations.openMocks(this); + * } + * + * @After public void releaseMocks() throws Exception { + * closeable.close(); * } * } * *

    * Read also about other annotations @{@link Spy}, @{@link Captor}, @{@link InjectMocks} *

    - * MockitoAnnotations.initMocks(this) method has to be called to initialize annotated fields. + * MockitoAnnotations.openMocks(this) method has to be called to initialize annotated fields. *

    - * In above example, initMocks() is called in @Before (JUnit4) method of test's base class. - * For JUnit3 initMocks() can go to setup() method of a base class. - * You can also put initMocks() in your JUnit runner (@RunWith) or use built-in runner: {@link MockitoJUnitRunner} + * In above example, openMocks() is called in @Before (JUnit4) method of test's base class. + * For JUnit3 openMocks() can go to setup() method of a base class. + * You can also put openMocks() in your JUnit runner (@RunWith) or use built-in runner: {@link MockitoJUnitRunner}. + * If static method mocks are used, it is required to close the initialization. Additionally, if using third-party + * mock-makers, other life-cycles might be handled by the open-release routine. */ public class MockitoAnnotations { @@ -58,8 +68,10 @@ public class MockitoAnnotations { * @{@link org.mockito.Mock}, @{@link Spy}, @{@link Captor}, @{@link InjectMocks} *

    * See examples in javadoc for {@link MockitoAnnotations} class. + * + * @return A closable to close when completing any tests in {@code testClass}. */ - public static void initMocks(Object testClass) { + public static AutoCloseable openMocks(Object testClass) { if (testClass == null) { throw new MockitoException( "testClass cannot be null. For info how to use @Mock annotations see examples in javadoc for MockitoAnnotations class"); @@ -67,6 +79,31 @@ public static void initMocks(Object testClass) { AnnotationEngine annotationEngine = new GlobalConfiguration().tryGetPluginAnnotationEngine(); - annotationEngine.process(testClass.getClass(), testClass); + return annotationEngine.process(testClass.getClass(), testClass); + } + + /** + * Initializes objects annotated with Mockito annotations for given testClass: + * @{@link org.mockito.Mock}, @{@link Spy}, @{@link Captor}, @{@link InjectMocks} + *

    + * See examples in javadoc for {@link MockitoAnnotations} class. + * + * @deprecated Use {@link MockitoAnnotations#openMocks(Object)} instead. + * This method is equivalent to {@code openMocks(testClass).close()}. + * The close method should however only be called after completed usage of {@code testClass}. + * If using static-mocks or custom {@link org.mockito.plugins.MockMaker}s, using this method might + * cause misbehavior of mocks injected into the test class. + */ + @Deprecated + public static void initMocks(Object testClass) { + try { + openMocks(testClass).close(); + } catch (Exception e) { + throw new MockitoException(join( + "Failed to release mocks", + "", + "This should not happen unless you are using a third-part mock maker" + ), e); + } } } diff --git a/src/main/java/org/mockito/MockitoSession.java b/src/main/java/org/mockito/MockitoSession.java index 6a529fde43..903fd0fdd0 100644 --- a/src/main/java/org/mockito/MockitoSession.java +++ b/src/main/java/org/mockito/MockitoSession.java @@ -67,7 +67,7 @@ *

    * Why to use {@code MockitoSession}? * What's the difference between {@code MockitoSession}, {@link MockitoJUnitRunner}, {@link MockitoRule} - * and traditional {@link MockitoAnnotations#initMocks(Object)}? + * and traditional {@link MockitoAnnotations#openMocks(Object)}? *

    * Great questions! * There is no need to use {@code MockitoSession} if you already use {@link MockitoJUnitRunner} or {@link MockitoRule}. @@ -78,7 +78,7 @@ * You can automatically take advantage of strict stubbing ({@link Strictness}), * automatic initialization of annotated mocks ({@link MockitoAnnotations}), * and extra validation ({@link Mockito#validateMockitoUsage()}). - * If you use Mockito annotations with {@link MockitoAnnotations#initMocks(Object)} + * If you use Mockito annotations with {@link MockitoAnnotations#openMocks(Object)} * but not Mockito runner/rule please try out Mockito's JUnit support (runner or rule) or * start using {@code MockitoSession}. You'll get cleaner tests and better productivity. *

    diff --git a/src/main/java/org/mockito/Spy.java b/src/main/java/org/mockito/Spy.java index b164c8f590..aa485a355d 100644 --- a/src/main/java/org/mockito/Spy.java +++ b/src/main/java/org/mockito/Spy.java @@ -25,9 +25,14 @@ * @Spy Foo spyOnFoo = new Foo("argument"); * //Instance for spying is created by mockito via reflection (only default constructors supported): * @Spy Bar spyOnBar; + * private AutoCloseable closeable; * @Before - * public void init(){ - * MockitoAnnotations.initMocks(this); + * public void init() { + * closeable = MockitoAnnotations.openMocks(this); + * } + * @After + * public void release() throws Exception { + * closeable.close(); * } * ... * } @@ -84,12 +89,13 @@ * * *

    - * One last warning : if you call MockitoAnnotations.initMocks(this) in a + * One last warning : if you call MockitoAnnotations.openMocks(this) in a * super class constructor then this will not work. It is because fields * in subclass are only instantiated after super class constructor has returned. * It's better to use @Before. - * Instead you can also put initMocks() in your JUnit runner (@RunWith) or use the built-in - * {@link MockitoJUnitRunner}. + * Instead you can also put openMocks() in your JUnit runner (@RunWith) or use the built-in + * {@link MockitoJUnitRunner}. Also, make sure to release any mocks after disposing your test class with a + * corresponding hook. *

    * *

    Note that the spy won't have any annotations of the spied type, because CGLIB won't rewrite them. @@ -98,7 +104,7 @@ * @see Mockito#spy(Object) * @see Mock * @see InjectMocks - * @see MockitoAnnotations#initMocks(Object) + * @see MockitoAnnotations#openMocks(Object) * @see MockitoJUnitRunner * @since 1.8.3 */ diff --git a/src/main/java/org/mockito/configuration/AnnotationEngine.java b/src/main/java/org/mockito/configuration/AnnotationEngine.java index d4f8fb0b1f..897878169a 100644 --- a/src/main/java/org/mockito/configuration/AnnotationEngine.java +++ b/src/main/java/org/mockito/configuration/AnnotationEngine.java @@ -9,7 +9,7 @@ /** * Configures mock creation logic behind @Mock, @Captor and @Spy annotations *

    - * If you are interested then see implementations or source code of {@link MockitoAnnotations#initMocks(Object)} + * If you are interested then see implementations or source code of {@link MockitoAnnotations#openMocks(Object)} * *

    This interface can be used to configure a different annotation engine through * {@link org.mockito.configuration.IMockitoConfiguration}, however this mechanism is being superseded by the new diff --git a/src/main/java/org/mockito/internal/MockedStaticImpl.java b/src/main/java/org/mockito/internal/MockedStaticImpl.java new file mode 100644 index 0000000000..784145d381 --- /dev/null +++ b/src/main/java/org/mockito/internal/MockedStaticImpl.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal; + +import org.mockito.Incubating; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockingDetails; +import org.mockito.Mockito; +import org.mockito.exceptions.base.MockitoAssertionError; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.creation.StaticMockControl; +import org.mockito.internal.debugging.LocationImpl; +import org.mockito.internal.listeners.VerificationStartedNotifier; +import org.mockito.internal.progress.MockingProgress; +import org.mockito.internal.stubbing.InvocationContainerImpl; +import org.mockito.internal.verification.MockAwareVerificationMode; +import org.mockito.internal.verification.VerificationDataImpl; +import org.mockito.invocation.Location; +import org.mockito.invocation.MockHandler; +import org.mockito.stubbing.OngoingStubbing; +import org.mockito.verification.VerificationMode; + +import static org.mockito.internal.exceptions.Reporter.*; +import static org.mockito.internal.progress.ThreadSafeMockingProgress.*; +import static org.mockito.internal.util.MockUtil.*; +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 StaticMockControl control; + + private boolean closed; + + private final Location location = new LocationImpl(); + + protected MockedStaticImpl(StaticMockControl control) { + this.control = control; + } + + @Override + public OngoingStubbing when(Verification verification) { + assertNotClosed(); + + try { + verification.apply(); + } catch (Throwable ignored) { + } + + MockingProgress mockingProgress = mockingProgress(); + mockingProgress.stubbingStarted(); + @SuppressWarnings("unchecked") + OngoingStubbing stubbing = (OngoingStubbing) mockingProgress.pullOngoingStubbing(); + if (stubbing == null) { + mockingProgress.reset(); + throw missingMethodInvocation(); + } + return stubbing; + } + + @Override + public void verify(VerificationMode mode, Verification verification) { + assertNotClosed(); + + MockingDetails mockingDetails = Mockito.mockingDetails(control.getType()); + MockHandler handler = mockingDetails.getMockHandler(); + + VerificationStartedNotifier.notifyVerificationStarted( + handler.getMockSettings().getVerificationStartedListeners(), mockingDetails); + + MockingProgress mockingProgress = mockingProgress(); + VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode); + mockingProgress.verificationStarted( + new MockAwareVerificationMode( + control.getType(), actualMode, mockingProgress.verificationListeners())); + + try { + verification.apply(); + } catch (MockitoException | MockitoAssertionError e) { + throw e; + } catch (Throwable t) { + throw new MockitoException( + join( + "An unexpected error occurred while verifying a static stub", + "", + "To correctly verify a stub, invoke a single static method of " + + control.getType().getName() + + " in the provided lambda.", + "For example, if a method 'sample' was defined, provide a lambda or anonymous class containing the code", + "", + "() -> " + control.getType().getSimpleName() + ".sample()", + "or", + control.getType().getSimpleName() + "::sample"), + t); + } + } + + @Override + public void reset() { + assertNotClosed(); + + MockingProgress mockingProgress = mockingProgress(); + mockingProgress.validateState(); + mockingProgress.reset(); + mockingProgress.resetOngoingStubbing(); + + resetMock(control.getType()); + } + + @Override + public void clearInvocations() { + assertNotClosed(); + + MockingProgress mockingProgress = mockingProgress(); + mockingProgress.validateState(); + mockingProgress.reset(); + mockingProgress.resetOngoingStubbing(); + + getInvocationContainer(control.getType()).clearInvocations(); + } + + @Override + public void verifyNoMoreInteractions() { + assertNotClosed(); + + mockingProgress().validateState(); + InvocationContainerImpl invocations = getInvocationContainer(control.getType()); + VerificationDataImpl data = new VerificationDataImpl(invocations, null); + noMoreInteractions().verify(data); + } + + @Override + public void verifyNoInteractions() { + assertNotClosed(); + + mockingProgress().validateState(); + InvocationContainerImpl invocations = getInvocationContainer(control.getType()); + VerificationDataImpl data = new VerificationDataImpl(invocations, null); + noInteractions().verify(data); + } + + @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/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java index 4353e0fbda..b0fd5316c9 100644 --- a/src/main/java/org/mockito/internal/MockitoCore.java +++ b/src/main/java/org/mockito/internal/MockitoCore.java @@ -7,6 +7,7 @@ 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; @@ -20,9 +21,11 @@ import org.mockito.InOrder; import org.mockito.MockSettings; +import org.mockito.MockedStatic; import org.mockito.MockingDetails; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.internal.creation.MockSettingsImpl; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.internal.invocation.finder.VerifiableInvocationsFinder; import org.mockito.internal.listeners.VerificationStartedNotifier; import org.mockito.internal.progress.MockingProgress; @@ -68,6 +71,22 @@ public T mock(Class typeToMock, MockSettings settings) { return mock; } + public MockedStatic mockStatic(Class classToMock, MockSettings settings) { + if (!MockSettingsImpl.class.isInstance(settings)) { + throw new IllegalArgumentException( + "Unexpected implementation of '" + + settings.getClass().getCanonicalName() + + "'\n" + + "At the moment, you cannot provide your own implementations of that class."); + } + MockSettingsImpl impl = MockSettingsImpl.class.cast(settings); + MockCreationSettings creationSettings = impl.buildStatic(classToMock); + StaticMockControl control = createStaticMock(classToMock, creationSettings); + control.enable(); + mockingProgress().mockingStarted(classToMock, creationSettings); + return new MockedStaticImpl<>(control); + } + public OngoingStubbing when(T methodCall) { MockingProgress mockingProgress = mockingProgress(); mockingProgress.stubbingStarted(); diff --git a/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java index 9b1c3767d0..24cb7a1cb2 100644 --- a/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/IndependentAnnotationEngine.java @@ -9,11 +9,14 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; import org.mockito.exceptions.base.MockitoException; import org.mockito.plugins.AnnotationEngine; @@ -60,18 +63,25 @@ private void registerAnnotationProcessor( } @Override - public void process(Class clazz, Object testInstance) { + public AutoCloseable process(Class clazz, Object testInstance) { + List> mockedStatics = 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 != null) { throwIfAlreadyAssigned(field, alreadyAssigned); alreadyAssigned = true; try { setField(testInstance, field, mock); } catch (Exception e) { + for (MockedStatic mockedStatic : mockedStatics) { + mockedStatic.close(); + } throw new MockitoException( "Problems setting field " + field.getName() @@ -82,6 +92,11 @@ public void process(Class clazz, Object testInstance) { } } } + return () -> { + for (MockedStatic mockedStatic : mockedStatics) { + mockedStatic.closeOnDemand(); + } + }; } void throwIfAlreadyAssigned(Field field, boolean alreadyAssigned) { diff --git a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java index ccc7ae4d85..2f6667015c 100644 --- a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java @@ -7,9 +7,12 @@ import static org.mockito.internal.util.collections.Sets.newMockSafeHashSet; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; +import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; import org.mockito.internal.configuration.injection.scanner.InjectMocksScanner; import org.mockito.internal.configuration.injection.scanner.MockScanner; @@ -39,29 +42,41 @@ public class InjectingAnnotationEngine * * @see org.mockito.plugins.AnnotationEngine#process(Class, Object) */ - public void process(Class clazz, Object testInstance) { - processIndependentAnnotations(testInstance.getClass(), testInstance); - processInjectMocks(testInstance.getClass(), testInstance); + public AutoCloseable process(Class clazz, Object testInstance) { + List closeables = new ArrayList<>(); + closeables.addAll(processIndependentAnnotations(testInstance.getClass(), testInstance)); + closeables.addAll(processInjectMocks(testInstance.getClass(), testInstance)); + return () -> { + for (AutoCloseable closeable : closeables) { + closeable.close(); + } + }; } - private void processInjectMocks(final Class clazz, final Object testInstance) { + private List processInjectMocks( + final Class clazz, final Object testInstance) { + List closeables = new ArrayList<>(); Class classContext = clazz; while (classContext != Object.class) { - injectMocks(testInstance); + closeables.add(injectMocks(testInstance)); classContext = classContext.getSuperclass(); } + return closeables; } - private void processIndependentAnnotations(final Class clazz, final Object testInstance) { + private List processIndependentAnnotations( + final Class clazz, final Object testInstance) { + List closeables = new ArrayList<>(); Class classContext = clazz; while (classContext != Object.class) { // this will create @Mocks, @Captors, etc: - delegate.process(classContext, testInstance); + closeables.add(delegate.process(classContext, testInstance)); // this will create @Spies: - spyAnnotationEngine.process(classContext, testInstance); + closeables.add(spyAnnotationEngine.process(classContext, testInstance)); classContext = classContext.getSuperclass(); } + return closeables; } /** @@ -73,7 +88,7 @@ private void processIndependentAnnotations(final Class clazz, final Object te * @param testClassInstance * Test class, usually this */ - public void injectMocks(final Object testClassInstance) { + private AutoCloseable injectMocks(final Object testClassInstance) { Class clazz = testClassInstance.getClass(); Set mockDependentFields = new HashSet(); Set mocks = newMockSafeHashSet(); @@ -87,6 +102,14 @@ public void injectMocks(final Object testClassInstance) { new DefaultInjectionEngine() .injectMocksOnFields(mockDependentFields, mocks, testClassInstance); + + return () -> { + for (Object mock : mocks) { + if (mock instanceof MockedStatic) { + ((MockedStatic) mock).closeOnDemand(); + } + } + }; } protected void onInjection( diff --git a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java index 2994f1c84e..c7f562a830 100644 --- a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java +++ b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java @@ -4,11 +4,18 @@ */ package org.mockito.internal.configuration; +import static org.mockito.internal.util.StringUtil.join; + import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import org.mockito.Mock; import org.mockito.MockSettings; +import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.Supplier; /** * Instantiates a mock on a field annotated by {@link Mock} @@ -16,10 +23,12 @@ public class MockAnnotationProcessor implements FieldAnnotationProcessor { @Override public Object process(Mock annotation, Field field) { - return processAnnotationForMock(annotation, field.getType(), field.getName()); + return processAnnotationForMock( + annotation, field.getType(), field::getGenericType, field.getName()); } - public static Object processAnnotationForMock(Mock annotation, Class type, String name) { + public static Object processAnnotationForMock( + Mock annotation, Class type, Supplier genericType, String name) { MockSettings mockSettings = Mockito.withSettings(); if (annotation.extraInterfaces().length > 0) { // never null mockSettings.extraInterfaces(annotation.extraInterfaces()); @@ -41,6 +50,29 @@ public static Object processAnnotationForMock(Mock annotation, Class type, St // see @Mock answer default value mockSettings.defaultAnswer(annotation.answer()); - return Mockito.mock(type, mockSettings); + + if (type == MockedStatic.class) { + return Mockito.mockStatic(inferStaticMock(genericType.get(), name), mockSettings); + } else { + return Mockito.mock(type, mockSettings); + } + } + + private static Class inferStaticMock(Type type, String name) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return (Class) parameterizedType.getRawType(); + } else { + throw new MockitoException( + 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", + "", + "@Mock MockedStatic", + "", + "as the type parameter")); + } } } diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java index 27ebc4a04d..b4686b30d8 100644 --- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java @@ -48,7 +48,7 @@ public class SpyAnnotationEngine implements AnnotationEngine, org.mockito.configuration.AnnotationEngine { @Override - public void process(Class context, Object testInstance) { + public AutoCloseable process(Class context, Object testInstance) { Field[] fields = context.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Spy.class) @@ -60,7 +60,7 @@ public void process(Class context, Object testInstance) { instance = field.get(testInstance); if (MockUtil.isMock(instance)) { // instance has been spied earlier - // for example happens when MockitoAnnotations.initMocks is called two + // for example happens when MockitoAnnotations.openMocks is called two // times. Mockito.reset(instance); } else if (instance != null) { @@ -78,6 +78,7 @@ public void process(Class context, Object testInstance) { } } } + return new NoAction(); } private static Object spyInstance(Field field, Object instance) { 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 34b8682a5c..cd8fcc5e3a 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java +++ b/src/main/java/org/mockito/internal/configuration/injection/SpyOnInjectedFieldsHandler.java @@ -36,7 +36,7 @@ protected boolean processInjection(Field field, Object fieldOwner, Set m Object instance = fieldReader.read(); if (MockUtil.isMock(instance)) { // A. instance has been spied earlier - // B. protect against multiple use of MockitoAnnotations.initMocks() + // B. protect against multiple use of MockitoAnnotations.openMocks() Mockito.reset(instance); } else { Object mock = diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java index dee7f45c06..da0321545d 100644 --- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java +++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java @@ -21,6 +21,7 @@ import java.util.Set; import org.mockito.MockSettings; +import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.internal.debugging.VerboseMockInvocationLogger; import org.mockito.internal.util.Checks; @@ -231,6 +232,11 @@ public MockCreationSettings build(Class typeToMock) { return validatedSettings(typeToMock, (CreationSettings) this); } + @Override + public MockCreationSettings buildStatic(Class classToMock) { + return validatedStaticSettings(classToMock, (CreationSettings) this); + } + @Override public MockSettings lenient() { this.lenient = true; @@ -254,12 +260,34 @@ private static CreationSettings validatedSettings( // TODO do we really need to copy the entire settings every time we create mock object? it // does not seem necessary. CreationSettings settings = new CreationSettings(source); - settings.setMockName(new MockNameImpl(source.getName(), typeToMock)); + settings.setMockName(new MockNameImpl(source.getName(), typeToMock, false)); settings.setTypeToMock(typeToMock); settings.setExtraInterfaces(prepareExtraInterfaces(source)); return settings; } + private static CreationSettings validatedStaticSettings( + Class classToMock, CreationSettings source) { + + if (classToMock.isPrimitive()) { + throw new MockitoException( + "Cannot create static mock of primitive type " + classToMock); + } + if (!source.getExtraInterfaces().isEmpty()) { + throw new MockitoException( + "Cannot specify additional interfaces for static mock of " + classToMock); + } + if (source.getSpiedInstance() != null) { + throw new MockitoException( + "Cannot specify spied instance for static mock of " + classToMock); + } + + CreationSettings settings = new CreationSettings(source); + settings.setMockName(new MockNameImpl(source.getName(), classToMock, true)); + settings.setTypeToMock(classToMock); + return settings; + } + private static Set> prepareExtraInterfaces(CreationSettings settings) { Set> interfaces = new HashSet>(settings.getExtraInterfaces()); if (settings.isSerializable()) { diff --git a/src/main/java/org/mockito/internal/creation/StaticMockControl.java b/src/main/java/org/mockito/internal/creation/StaticMockControl.java new file mode 100644 index 0000000000..7bc90f3ee4 --- /dev/null +++ b/src/main/java/org/mockito/internal/creation/StaticMockControl.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.creation; + +import org.mockito.Incubating; + +@Incubating +public interface StaticMockControl { + + Class getType(); + + void enable(); + + void disable(); +} 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 3b124dcaab..1a7bff3fd5 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java @@ -5,6 +5,7 @@ package org.mockito.internal.creation.bytebuddy; import org.mockito.Incubating; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; @@ -45,4 +46,10 @@ public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings public TypeMockability isTypeMockable(Class type) { return defaultByteBuddyMockMaker.isTypeMockable(type); } + + @Override + public StaticMockControl createStaticMock( + Class type, MockCreationSettings settings, MockHandler handler) { + return defaultByteBuddyMockMaker.createStaticMock(type, settings, handler); + } } 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 a131c04bc7..b87d7cb6b7 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/BytecodeGenerator.java @@ -7,4 +7,6 @@ public interface BytecodeGenerator { Class mockClass(MockFeatures features); + + void mockClassStatic(Class type); } 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 d110451e3d..06840d26ee 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java @@ -13,6 +13,9 @@ import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -25,7 +28,9 @@ import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.base.MockitoInitializationException; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.internal.util.Platform; +import org.mockito.internal.util.concurrent.DetachedThreadLocal; import org.mockito.internal.util.concurrent.WeakConcurrentMap; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; @@ -182,6 +187,9 @@ public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker, InlineM private final WeakConcurrentMap mocks = new WeakConcurrentMap.WithInlinedExpunction(); + private final DetachedThreadLocal, MockMethodInterceptor>> mockedStatics = + new DetachedThreadLocal<>(DetachedThreadLocal.Cleaner.INLINE); + public InlineByteBuddyMockMaker() { if (INITIALIZATION_ERROR != null) { throw new MockitoInitializationException( @@ -195,7 +203,7 @@ public InlineByteBuddyMockMaker() { } bytecodeGenerator = new TypeCachingBytecodeGenerator( - new InlineBytecodeGenerator(INSTRUMENTATION, mocks), true); + new InlineBytecodeGenerator(INSTRUMENTATION, mocks, mockedStatics), true); } @Override @@ -288,7 +296,13 @@ private RuntimeException prettifyFailure( @Override public MockHandler getHandler(Object mock) { - MockMethodInterceptor interceptor = mocks.get(mock); + MockMethodInterceptor interceptor; + if (mock instanceof Class) { + Map, MockMethodInterceptor> interceptors = mockedStatics.get(); + interceptor = interceptors != null ? interceptors.get(mock) : null; + } else { + interceptor = mocks.get(mock); + } if (interceptor == null) { return null; } else { @@ -300,19 +314,41 @@ public MockHandler getHandler(Object mock) { public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) { MockMethodInterceptor mockMethodInterceptor = new MockMethodInterceptor(newHandler, settings); - mocks.put(mock, mockMethodInterceptor); - if (mock instanceof MockAccess) { - ((MockAccess) mock).setMockitoInterceptor(mockMethodInterceptor); + if (mock instanceof Class) { + Map, MockMethodInterceptor> interceptors = mockedStatics.get(); + if (interceptors == null || !interceptors.containsKey(mock)) { + throw new MockitoException( + "Cannot reset " + + mock + + " which is not currently registered as a static mock"); + } + interceptors.put((Class) mock, mockMethodInterceptor); + } else { + if (!mocks.containsKey(mock)) { + throw new MockitoException( + "Cannot reset " + mock + " which is not currently registered as a mock"); + } + mocks.put(mock, mockMethodInterceptor); + if (mock instanceof MockAccess) { + ((MockAccess) mock).setMockitoInterceptor(mockMethodInterceptor); + } } } @Override public void clearMock(Object mock) { - mocks.remove(mock); + if (mock instanceof Class) { + for (Map, ?> entry : mockedStatics.getBackingMap().target.values()) { + entry.remove(mock); + } + } else { + mocks.remove(mock); + } } @Override public void clearAllMocks() { + mockedStatics.getBackingMap().clear(); mocks.clear(); } @@ -339,4 +375,79 @@ public String nonMockableReason() { } }; } + + @Override + public StaticMockControl createStaticMock( + Class type, MockCreationSettings settings, MockHandler handler) { + if (type == ConcurrentHashMap.class) { + throw new MockitoException( + "It is not possible to mock static methods of ConcurrentHashMap " + + "to avoid infinitive loops within Mockito's implementation of static mock handling"); + } + + bytecodeGenerator.mockClassStatic(type); + + Map, MockMethodInterceptor> interceptors = mockedStatics.get(); + if (interceptors == null) { + interceptors = new WeakHashMap<>(); + mockedStatics.set(interceptors); + } + + return new InlineStaticMockControl<>(type, interceptors, settings, handler); + } + + private static class InlineStaticMockControl implements StaticMockControl { + + private final Class type; + + private final Map, MockMethodInterceptor> interceptors; + + private final MockCreationSettings settings; + private final MockHandler handler; + + private InlineStaticMockControl( + Class type, + Map, MockMethodInterceptor> interceptors, + MockCreationSettings settings, + MockHandler handler) { + this.type = type; + this.interceptors = interceptors; + this.settings = settings; + this.handler = handler; + } + + @Override + public Class getType() { + return type; + } + + @Override + public void enable() { + if (interceptors.putIfAbsent(type, new MockMethodInterceptor(handler, settings)) + != 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)")); + } + } + } } 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 dfca195b4d..187f1ea945 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java @@ -38,6 +38,7 @@ import net.bytebuddy.utility.RandomString; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher; +import org.mockito.internal.util.concurrent.DetachedThreadLocal; import org.mockito.internal.util.concurrent.WeakConcurrentMap; import org.mockito.internal.util.concurrent.WeakConcurrentSet; import org.mockito.mock.SerializableMode; @@ -63,7 +64,7 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran private final Instrumentation instrumentation; private final ByteBuddy byteBuddy; - private final WeakConcurrentSet> mocked; + private final WeakConcurrentSet> mocked, flatMocked; private final BytecodeGenerator subclassEngine; private final AsmVisitorWrapper mockTransformer; @@ -73,7 +74,8 @@ public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTran public InlineBytecodeGenerator( Instrumentation instrumentation, - WeakConcurrentMap mocks) { + WeakConcurrentMap mocks, + DetachedThreadLocal, MockMethodInterceptor>> mockedStatics) { preload(); this.instrumentation = instrumentation; byteBuddy = @@ -81,7 +83,8 @@ public InlineBytecodeGenerator( .with(TypeValidation.DISABLED) .with(Implementation.Context.Disabled.Factory.INSTANCE) .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE); - mocked = new WeakConcurrentSet>(WeakConcurrentSet.Cleaner.INLINE); + mocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.INLINE); + flatMocked = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.INLINE); String identifier = RandomString.make(); subclassEngine = new TypeCachingBytecodeGenerator( @@ -110,6 +113,11 @@ public InlineBytecodeGenerator( Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class)) + .method( + isStatic(), + Advice.withCustomMapping() + .bind(MockMethodAdvice.Identifier.class, identifier) + .to(MockMethodAdvice.ForStatic.class)) .method( isHashCode(), Advice.withCustomMapping() @@ -141,7 +149,8 @@ public InlineBytecodeGenerator( this.getModule = getModule; this.canRead = canRead; this.redefineModule = redefineModule; - MockMethodDispatcher.set(identifier, new MockMethodAdvice(mocks, identifier)); + MockMethodDispatcher.set( + identifier, new MockMethodAdvice(mocks, mockedStatics, identifier)); instrumentation.addTransformer(this, true); } @@ -182,27 +191,46 @@ public Class mockClass(MockFeatures features) { checkSupportedCombination(subclassingRequired, features); + Set> types = new HashSet<>(); + types.add(features.mockedType); + types.addAll(features.interfaces); synchronized (this) { - triggerRetransformation(features); + triggerRetransformation(types, false); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; } - private void triggerRetransformation(MockFeatures features) { - Set> types = new HashSet>(); - Class type = features.mockedType; - do { - if (mocked.add(type)) { - types.add(type); - addInterfaces(types, type.getInterfaces()); + @Override + public void mockClassStatic(Class type) { + triggerRetransformation(Collections.singleton(type), true); + } + + private void triggerRetransformation(Set> types, boolean flat) { + Set> targets = new HashSet>(); + + for (Class type : types) { + if (flat) { + if (!mocked.contains(type) && flatMocked.add(type)) { + targets.add(type); + } + } else { + do { + if (mocked.add(type)) { + if (!flatMocked.remove(type)) { + targets.add(type); + } + addInterfaces(targets, type.getInterfaces()); + } + type = type.getSuperclass(); + } while (type != null); } - type = type.getSuperclass(); - } while (type != null); - if (!types.isEmpty()) { + } + + if (!targets.isEmpty()) { try { - assureCanReadMockito(types); - instrumentation.retransformClasses(types.toArray(new Class[types.size()])); + assureCanReadMockito(targets); + instrumentation.retransformClasses(targets.toArray(new Class[targets.size()])); Throwable throwable = lastException; if (throwable != null) { throw new IllegalStateException( @@ -215,10 +243,11 @@ private void triggerRetransformation(MockFeatures features) { throwable); } } catch (Exception exception) { - for (Class failed : types) { + for (Class failed : targets) { mocked.remove(failed); + flatMocked.remove(failed); } - throw new MockitoException("Could not modify all classes " + types, exception); + throw new MockitoException("Could not modify all classes " + targets, exception); } finally { lastException = null; } @@ -281,7 +310,9 @@ private void checkSupportedCombination( private void addInterfaces(Set> types, Class[] interfaces) { for (Class type : interfaces) { if (mocked.add(type)) { - types.add(type); + if (!flatMocked.remove(type)) { + types.add(type); + } addInterfaces(types, type.getInterfaces()); } } @@ -296,6 +327,7 @@ public byte[] transform( byte[] classfileBuffer) { if (classBeingRedefined == null || !mocked.contains(classBeingRedefined) + && !flatMocked.contains(classBeingRedefined) || EXCLUDES.contains(classBeingRedefined)) { return null; } else { 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 87640a4441..072a958577 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java @@ -13,6 +13,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Map; import java.util.concurrent.Callable; import net.bytebuddy.asm.Advice; @@ -30,11 +31,13 @@ import org.mockito.internal.invocation.SerializableMethod; import org.mockito.internal.invocation.mockref.MockReference; import org.mockito.internal.invocation.mockref.MockWeakReference; +import org.mockito.internal.util.concurrent.DetachedThreadLocal; import org.mockito.internal.util.concurrent.WeakConcurrentMap; public class MockMethodAdvice extends MockMethodDispatcher { private final WeakConcurrentMap interceptors; + private final DetachedThreadLocal, MockMethodInterceptor>> mockedStatics; private final String identifier; @@ -44,8 +47,11 @@ public class MockMethodAdvice extends MockMethodDispatcher { new WeakConcurrentMap.WithInlinedExpunction, SoftReference>(); public MockMethodAdvice( - WeakConcurrentMap interceptors, String identifier) { + WeakConcurrentMap interceptors, + DetachedThreadLocal, MockMethodInterceptor>> mockedStatics, + String identifier) { this.interceptors = interceptors; + this.mockedStatics = mockedStatics; this.identifier = identifier; } @@ -120,6 +126,24 @@ public Callable handle(Object instance, Method origin, Object[] arguments) th new LocationImpl(new Throwable(), true))); } + @Override + public Callable handleStatic(Class type, Method origin, Object[] arguments) + throws Throwable { + Map, MockMethodInterceptor> interceptors = mockedStatics.get(); + if (interceptors == null || !interceptors.containsKey(type)) { + return null; + } + return new ReturnValueWrapper( + interceptors + .get(type) + .doIntercept( + type, + origin, + arguments, + new StaticMethodCall(selfCallInfo, type, origin, arguments), + new LocationImpl(new Throwable(), true))); + } + @Override public boolean isMock(Object instance) { // We need to exclude 'interceptors.target' explicitly to avoid a recursive check on whether @@ -129,7 +153,16 @@ public boolean isMock(Object instance) { @Override public boolean isMocked(Object instance) { - return selfCallInfo.checkSuperCall(instance) && isMock(instance); + return selfCallInfo.checkSelfCall(instance) && isMock(instance); + } + + @Override + public boolean isMockedStatic(Class type) { + if (!selfCallInfo.checkSelfCall(type)) { + return false; + } + Map, ?> interceptors = mockedStatics.get(); + return interceptors != null && interceptors.containsKey(type); } @Override @@ -230,6 +263,39 @@ public Object invoke() throws Throwable { } } + private static class StaticMethodCall implements RealMethod { + + private final SelfCallInfo selfCallInfo; + + private final Class type; + + private final Method origin; + + private final Object[] arguments; + + private StaticMethodCall( + SelfCallInfo selfCallInfo, Class type, Method origin, Object[] arguments) { + this.selfCallInfo = selfCallInfo; + this.type = type; + this.origin = origin; + this.arguments = arguments; + } + + @Override + public boolean isInvokable() { + return true; + } + + @Override + public Object invoke() throws Throwable { + if (!Modifier.isPublic(type.getModifiers() & origin.getModifiers())) { + origin.setAccessible(true); + } + selfCallInfo.set(type); + return tryInvoke(origin, null, arguments); + } + } + private static Object tryInvoke(Method origin, Object instance, Object[] arguments) throws Throwable { try { @@ -268,7 +334,7 @@ Object replace(Object value) { return current; } - boolean checkSuperCall(Object value) { + boolean checkSelfCall(Object value) { if (value == get()) { set(null); return false; @@ -324,6 +390,36 @@ private static void enter( } } + static class ForStatic { + + @SuppressWarnings("unused") + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + private static Callable enter( + @Identifier String identifier, + @Advice.Origin Class type, + @Advice.Origin Method origin, + @Advice.AllArguments Object[] arguments) + throws Throwable { + MockMethodDispatcher dispatcher = MockMethodDispatcher.getStatic(identifier, type); + if (dispatcher == null || !dispatcher.isMockedStatic(type)) { + return null; + } else { + return dispatcher.handleStatic(type, origin, arguments); + } + } + + @SuppressWarnings({"unused", "UnusedAssignment"}) + @Advice.OnMethodExit + private static void exit( + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned, + @Advice.Enter Callable mocked) + throws Throwable { + if (mocked != null) { + returned = mocked.call(); + } + } + } + public static class ForReadObject { @SuppressWarnings("unused") diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java index e112d9e281..54a6573b6f 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java @@ -11,6 +11,7 @@ import org.mockito.creation.instance.Instantiator; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.internal.util.Platform; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; @@ -179,4 +180,15 @@ public String nonMockableReason() { } }; } + + @Override + public StaticMockControl createStaticMock( + Class type, MockCreationSettings settings, MockHandler handler) { + throw new MockitoException( + join( + "The regular, subclass-based MockMaker does not support the creation of static mocks", + "", + "To enable static mocks, you need to use Mockito's inline mock maker which is 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'.")); + } } 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 f323f23d44..4a65643bc5 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -203,6 +203,11 @@ public Class mockClass(MockFeatures features) { .getLoaded(); } + @Override + public void mockClassStatic(Class type) { + throw new MockitoException("The subclass byte code generator cannot create static 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 18ca2423a6..76bf44dca8 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java @@ -57,6 +57,11 @@ public Class call() throws Exception { } } + @Override + public void mockClassStatic(Class type) { + bytecodeGenerator.mockClassStatic(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 6c077cfd9e..f1b49051e0 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 @@ -11,29 +11,42 @@ public abstract class MockMethodDispatcher { - private static final ConcurrentMap INSTANCE = - new ConcurrentHashMap(); + private static final ConcurrentMap DISPATCHERS = + new ConcurrentHashMap<>(); public static MockMethodDispatcher get(String identifier, Object mock) { - if (mock - == INSTANCE) { // Avoid endless loop if ConcurrentHashMap was redefined to check for - // being a mock. + if (mock == DISPATCHERS) { + // Avoid endless loop if ConcurrentHashMap was redefined to check for being a mock. return null; } else { - return INSTANCE.get(identifier); + return DISPATCHERS.get(identifier); + } + } + + public static MockMethodDispatcher getStatic(String identifier, Class type) { + if (MockMethodDispatcher.class.isAssignableFrom(type) || type == ConcurrentHashMap.class) { + // Avoid endless loop for lookups of self. + return null; + } else { + return DISPATCHERS.get(identifier); } } public static void set(String identifier, MockMethodDispatcher dispatcher) { - INSTANCE.putIfAbsent(identifier, dispatcher); + DISPATCHERS.putIfAbsent(identifier, dispatcher); } 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 boolean isMock(Object instance); public abstract boolean isMocked(Object instance); + public abstract boolean isMockedStatic(Class type); + public abstract boolean isOverridden(Object instance, Method origin); } diff --git a/src/main/java/org/mockito/internal/exceptions/Reporter.java b/src/main/java/org/mockito/internal/exceptions/Reporter.java index b491a7fd3a..0a182178d3 100644 --- a/src/main/java/org/mockito/internal/exceptions/Reporter.java +++ b/src/main/java/org/mockito/internal/exceptions/Reporter.java @@ -147,7 +147,7 @@ public static MockitoException nullPassedToVerify() { " verify(mock, times(10)).someMethod();", " verify(mock, atLeastOnce()).someMethod();", " not: verify(mock.someMethod());", - "Also, if you use @Mock annotation don't miss initMocks()")); + "Also, if you use @Mock annotation don't miss openMocks()")); } public static MockitoException notAMockPassedToWhenMethod() { @@ -164,7 +164,7 @@ public static MockitoException nullPassedToWhenMethod() { "Argument passed to when() is null!", "Example of correct stubbing:", " doThrow(new RuntimeException()).when(mock).someMethod();", - "Also, if you use @Mock annotation don't miss initMocks()")); + "Also, if you use @Mock annotation don't miss openMocks()")); } public static MockitoException mocksHaveToBePassedToVerifyNoMoreInteractions() { @@ -740,7 +740,7 @@ public static MockitoException cannotInitializeForSpyAnnotation( "Examples of correct usage of @Spy:", " @Spy List mock = new LinkedList();", " @Spy Foo foo; //only if Foo has parameterless constructor", - " //also, don't forget about MockitoAnnotations.initMocks();", + " //also, don't forget about MockitoAnnotations.openMocks();", ""), details); } diff --git a/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java b/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java index 5e60099726..dfd5acce9c 100644 --- a/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java +++ b/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java @@ -4,11 +4,10 @@ */ package org.mockito.internal.framework; -import java.util.List; - import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.misusing.RedundantListenerException; import org.mockito.internal.exceptions.Reporter; import org.mockito.internal.junit.TestFinishedEvent; @@ -16,11 +15,16 @@ import org.mockito.plugins.MockitoLogger; import org.mockito.quality.Strictness; +import java.util.ArrayList; +import java.util.List; + public class DefaultMockitoSession implements MockitoSession { private final String name; private final UniversalTestListener listener; + private final List closeables = new ArrayList<>(); + public DefaultMockitoSession( List testClassInstances, String name, @@ -36,10 +40,12 @@ public DefaultMockitoSession( } try { for (Object testClassInstance : testClassInstances) { - MockitoAnnotations.initMocks(testClassInstance); + closeables.add(MockitoAnnotations.openMocks(testClassInstance)); } } catch (RuntimeException e) { - // clean up in case 'initMocks' fails + release(); + + // clean up in case 'openMocks' fails listener.setListenerDirty(); throw e; } @@ -52,11 +58,15 @@ public void setStrictness(Strictness strictness) { @Override public void finishMocking() { + release(); + finishMocking(null); } @Override public void finishMocking(final Throwable failure) { + release(); + // Cleaning up the state, we no longer need the listener hooked up // The listener implements MockCreationListener and at this point // we no longer need to listen on mock creation events. We are wrapping up the session @@ -82,4 +92,14 @@ public String getTestName() { Mockito.validateMockitoUsage(); } } + + private void release() { + for (AutoCloseable closeable : closeables) { + try { + closeable.close(); + } catch (Exception e) { + throw new MockitoException("Failed to release mocks", e); + } + } + } } diff --git a/src/main/java/org/mockito/internal/invocation/InterceptedInvocation.java b/src/main/java/org/mockito/internal/invocation/InterceptedInvocation.java index 4ef871dce5..57bb4b71e7 100644 --- a/src/main/java/org/mockito/internal/invocation/InterceptedInvocation.java +++ b/src/main/java/org/mockito/internal/invocation/InterceptedInvocation.java @@ -123,18 +123,6 @@ public T getArgument(int index) { return (T) arguments[index]; } - public MockReference getMockRef() { - return mockRef; - } - - public MockitoMethod getMockitoMethod() { - return mockitoMethod; - } - - public RealMethod getRealMethod() { - return realMethod; - } - @Override public List getArgumentsAsMatchers() { return argumentsToMatchers(getArguments()); diff --git a/src/main/java/org/mockito/internal/junit/JUnitSessionStore.java b/src/main/java/org/mockito/internal/junit/JUnitSessionStore.java index fa065acd9c..a5c7687657 100644 --- a/src/main/java/org/mockito/internal/junit/JUnitSessionStore.java +++ b/src/main/java/org/mockito/internal/junit/JUnitSessionStore.java @@ -26,6 +26,7 @@ class JUnitSessionStore { Statement createStatement(final Statement base, final String methodName, final Object target) { return new Statement() { public void evaluate() throws Throwable { + AutoCloseable closeable; if (session == null) { session = Mockito.mockitoSession() @@ -34,11 +35,15 @@ public void evaluate() throws Throwable { .logger(new MockitoSessionLoggerAdapter(logger)) .initMocks(target) .startMocking(); + closeable = null; } else { - MockitoAnnotations.initMocks(target); + closeable = MockitoAnnotations.openMocks(target); } Throwable testFailure = evaluateSafely(base); session.finishMocking(testFailure); + if (closeable != null) { + closeable.close(); + } if (testFailure != null) { throw testFailure; } diff --git a/src/main/java/org/mockito/internal/runners/DefaultInternalRunner.java b/src/main/java/org/mockito/internal/runners/DefaultInternalRunner.java index f81b01560b..d233cbe45d 100644 --- a/src/main/java/org/mockito/internal/runners/DefaultInternalRunner.java +++ b/src/main/java/org/mockito/internal/runners/DefaultInternalRunner.java @@ -40,14 +40,23 @@ protected Statement withBefores( return new Statement() { @Override public void evaluate() throws Throwable { + AutoCloseable closeable; if (mockitoTestListener == null) { // get new test listener and add it to the framework mockitoTestListener = listenerSupplier.get(); Mockito.framework().addListener(mockitoTestListener); // init annotated mocks before tests - MockitoAnnotations.initMocks(target); + closeable = MockitoAnnotations.openMocks(target); + } else { + closeable = null; + } + try { + base.evaluate(); + } finally { + if (closeable != null) { + closeable.close(); + } } - base.evaluate(); } }; } diff --git a/src/main/java/org/mockito/internal/util/MockNameImpl.java b/src/main/java/org/mockito/internal/util/MockNameImpl.java index a06975871d..ef0307fc11 100644 --- a/src/main/java/org/mockito/internal/util/MockNameImpl.java +++ b/src/main/java/org/mockito/internal/util/MockNameImpl.java @@ -15,9 +15,9 @@ public class MockNameImpl implements MockName, Serializable { private boolean defaultName; @SuppressWarnings("unchecked") - public MockNameImpl(String mockName, Class classToMock) { + public MockNameImpl(String mockName, Class type, boolean mockedStatic) { if (mockName == null) { - this.mockName = toInstanceName(classToMock); + this.mockName = mockedStatic ? toClassName(type) : toInstanceName(type); this.defaultName = true; } else { this.mockName = mockName; @@ -38,6 +38,15 @@ private static String toInstanceName(Class clazz) { return className.substring(0, 1).toLowerCase() + className.substring(1); } + private static String toClassName(Class clazz) { + String className = clazz.getSimpleName(); + if (className.length() == 0) { + // it's an anonymous class, let's get name from the parent + className = clazz.getSuperclass().getSimpleName() + "$"; + } + return className + ".class"; + } + public boolean isDefault() { return defaultName; } diff --git a/src/main/java/org/mockito/internal/util/MockUtil.java b/src/main/java/org/mockito/internal/util/MockUtil.java index 7dec584f4e..66bee0beac 100644 --- a/src/main/java/org/mockito/internal/util/MockUtil.java +++ b/src/main/java/org/mockito/internal/util/MockUtil.java @@ -9,6 +9,7 @@ import org.mockito.Mockito; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.internal.stubbing.InvocationContainerImpl; import org.mockito.internal.util.reflection.LenientCopyTool; @@ -102,4 +103,10 @@ public static void maybeRedefineMockName(Object mock, String newName) { public static MockCreationSettings getMockSettings(Object mock) { return getMockHandler(mock).getMockSettings(); } + + public static StaticMockControl createStaticMock( + Class type, MockCreationSettings settings) { + MockHandler handler = createMockHandler(settings); + return mockMaker.createStaticMock(type, settings, handler); + } } diff --git a/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentSet.java b/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentSet.java index e9bc1cea2d..f3417c020e 100644 --- a/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentSet.java +++ b/src/main/java/org/mockito/internal/util/concurrent/WeakConcurrentSet.java @@ -53,7 +53,7 @@ public boolean contains(V value) { * @return {@code true} if the value is contained in the set. */ public boolean remove(V value) { - return target.remove(value); + return target.remove(value) != null; } /** diff --git a/src/main/java/org/mockito/junit/MockitoJUnitRunner.java b/src/main/java/org/mockito/junit/MockitoJUnitRunner.java index 2c86bd0f15..3d2e8675e5 100644 --- a/src/main/java/org/mockito/junit/MockitoJUnitRunner.java +++ b/src/main/java/org/mockito/junit/MockitoJUnitRunner.java @@ -37,7 +37,7 @@ * To opt-out from this feature, use {@code}@RunWith(MockitoJUnitRunner.Silent.class){@code} *
  • * Initializes mocks annotated with {@link Mock}, - * so that explicit usage of {@link MockitoAnnotations#initMocks(Object)} is not necessary. + * so that explicit usage of {@link MockitoAnnotations#openMocks(Object)} is not necessary. * Mocks are initialized before each test method. *
  • * Validates framework usage after each test method. See javadoc for {@link Mockito#validateMockitoUsage()}. diff --git a/src/main/java/org/mockito/junit/MockitoRule.java b/src/main/java/org/mockito/junit/MockitoRule.java index 98de12ed10..276d7ce7e9 100644 --- a/src/main/java/org/mockito/junit/MockitoRule.java +++ b/src/main/java/org/mockito/junit/MockitoRule.java @@ -37,7 +37,7 @@ * See also {@link MockitoHint}. *
  • * Initializes mocks annotated with {@link org.mockito.Mock}, - * so that explicit usage of {@link MockitoAnnotations#initMocks(Object)} is not necessary. + * so that explicit usage of {@link MockitoAnnotations#openMocks(Object)} is not necessary. * Mocks are initialized before each test method. *
  • * Validates framework usage after each test method. See javadoc for {@link org.mockito.Mockito#validateMockitoUsage()}. diff --git a/src/main/java/org/mockito/plugins/AnnotationEngine.java b/src/main/java/org/mockito/plugins/AnnotationEngine.java index c60f477b9f..c6365e9f18 100644 --- a/src/main/java/org/mockito/plugins/AnnotationEngine.java +++ b/src/main/java/org/mockito/plugins/AnnotationEngine.java @@ -13,7 +13,7 @@ * or replace mockito default engine. * *

    - * If you are interested then see implementations or source code of {@link org.mockito.MockitoAnnotations#initMocks(Object)} + * If you are interested then see implementations or source code of {@link org.mockito.MockitoAnnotations#openMocks(Object)} * *

    This plugin mechanism supersedes the {@link org.mockito.configuration.IMockitoConfiguration} * in regard of switching mockito components. @@ -25,5 +25,11 @@ public interface AnnotationEngine { * @param clazz Class where to extract field information, check implementation for details * @param testInstance Test instance */ - void process(Class clazz, Object testInstance); + AutoCloseable process(Class clazz, Object testInstance); + + class NoAction implements AutoCloseable { + + @Override + public void close() {} + } } diff --git a/src/main/java/org/mockito/plugins/MockMaker.java b/src/main/java/org/mockito/plugins/MockMaker.java index f2798ed8bf..29f3c7d77b 100644 --- a/src/main/java/org/mockito/plugins/MockMaker.java +++ b/src/main/java/org/mockito/plugins/MockMaker.java @@ -5,6 +5,7 @@ package org.mockito.plugins; import org.mockito.Incubating; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; @@ -108,6 +109,26 @@ public interface MockMaker { @Incubating TypeMockability isTypeMockable(Class type); + /** + * If you want to provide your own implementation of {@code MockMaker} this method should: + *

      + *
    • Alter the supplied class to only change its behavior in the current thread.
    • + *
    • Only alters the static method's behavior after being enabled.
    • + *
    • Stops the altered behavior when disabled.
    • + *
    + * + * @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 Type of the mock to return, actually the settings.getTypeToMock. + * @return A control for the static mock. + * @since 3.4.0 + */ + @Incubating + StaticMockControl createStaticMock( + Class type, MockCreationSettings settings, MockHandler handler); + /** * Carries the mockability information * diff --git a/src/main/java/org/mockito/session/MockitoSessionBuilder.java b/src/main/java/org/mockito/session/MockitoSessionBuilder.java index b17cfe77d6..648ca9d41e 100644 --- a/src/main/java/org/mockito/session/MockitoSessionBuilder.java +++ b/src/main/java/org/mockito/session/MockitoSessionBuilder.java @@ -26,10 +26,10 @@ public interface MockitoSessionBuilder { * like {@link org.mockito.Mock}. * When this method is invoked it does not perform initialization of mocks on the spot! * Only when {@link #startMocking()} is invoked then annotated fields will be initialized. - * Traditional API to initialize mocks, the {@link MockitoAnnotations#initMocks(Object)} method + * Traditional API to initialize mocks, the {@link MockitoAnnotations#openMocks(Object)} method * has limited support for driving cleaner tests because it does not support configuring {@link Strictness}. * Want cleaner tests and better productivity? - * Migrate from {@link MockitoAnnotations#initMocks(Object)} + * Migrate from {@link MockitoAnnotations#openMocks(Object)} * to {@link MockitoSession}! *

    * This method may be called multiple times to add multiple, e.g. nested, test class instances. diff --git a/src/test/java/org/mockito/MockitoTest.java b/src/test/java/org/mockito/MockitoTest.java index 008bf7f579..23df60e08e 100644 --- a/src/test/java/org/mockito/MockitoTest.java +++ b/src/test/java/org/mockito/MockitoTest.java @@ -11,6 +11,7 @@ import java.util.List; import org.junit.Test; +import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.exceptions.misusing.NullInsteadOfMockException; import org.mockito.internal.creation.MockSettingsImpl; @@ -64,6 +65,12 @@ public void shouldValidateMockWhenCreatingInOrderObject() { Mockito.inOrder("notMock"); } + @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) + @Test(expected = MockitoException.class) + public void shouldGiveExplantionOnStaticMockingWithoutInlineMockMaker() { + Mockito.mockStatic(Object.class); + } + @Test public void shouldStartingMockSettingsContainDefaultBehavior() { // when diff --git a/src/test/java/org/mockito/internal/configuration/GlobalConfigurationTest.java b/src/test/java/org/mockito/internal/configuration/GlobalConfigurationTest.java index 79d4fedd21..d5590ccfa6 100644 --- a/src/test/java/org/mockito/internal/configuration/GlobalConfigurationTest.java +++ b/src/test/java/org/mockito/internal/configuration/GlobalConfigurationTest.java @@ -59,6 +59,8 @@ public void reset_annotation_engine() { private static class CustomAnnotationEngine implements AnnotationEngine { @Override - public void process(Class clazz, Object testInstance) {} + public AutoCloseable process(Class clazz, Object testInstance) { + return new NoAction(); + } } } diff --git a/src/test/java/org/mockito/internal/util/DefaultMockingDetailsTest.java b/src/test/java/org/mockito/internal/util/DefaultMockingDetailsTest.java index f79818db41..b199054e3f 100644 --- a/src/test/java/org/mockito/internal/util/DefaultMockingDetailsTest.java +++ b/src/test/java/org/mockito/internal/util/DefaultMockingDetailsTest.java @@ -38,7 +38,7 @@ public class DefaultMockingDetailsTest { @Before public void before() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/org/mockito/internal/util/MockNameImplTest.java b/src/test/java/org/mockito/internal/util/MockNameImplTest.java index e0bc019dc4..583bd7ac06 100644 --- a/src/test/java/org/mockito/internal/util/MockNameImplTest.java +++ b/src/test/java/org/mockito/internal/util/MockNameImplTest.java @@ -14,25 +14,51 @@ public class MockNameImplTest extends TestBase { @Test public void shouldProvideTheNameForClass() throws Exception { // when - String name = new MockNameImpl(null, SomeClass.class).toString(); + String name = new MockNameImpl(null, SomeClass.class, false).toString(); // then assertEquals("someClass", name); } + @Test + public void shouldProvideTheNameForClassOnStaticMock() throws Exception { + // when + String name = new MockNameImpl(null, SomeClass.class, true).toString(); + // then + assertEquals("SomeClass.class", name); + } + @Test public void shouldProvideTheNameForAnonymousClass() throws Exception { // given SomeInterface anonymousInstance = new SomeInterface() {}; // when - String name = new MockNameImpl(null, anonymousInstance.getClass()).toString(); + String name = new MockNameImpl(null, anonymousInstance.getClass(), false).toString(); // then assertEquals("someInterface", name); } + @Test + public void shouldProvideTheNameForAnonymousClassOnStatic() throws Exception { + // given + SomeInterface anonymousInstance = new SomeInterface() {}; + // when + String name = new MockNameImpl(null, anonymousInstance.getClass(), true).toString(); + // then + assertEquals("SomeInterface$.class", name); + } + @Test public void shouldProvideTheGivenName() throws Exception { // when - String name = new MockNameImpl("The Hulk", SomeClass.class).toString(); + String name = new MockNameImpl("The Hulk", SomeClass.class, false).toString(); + // then + assertEquals("The Hulk", name); + } + + @Test + public void shouldProvideTheGivenNameOnStatic() throws Exception { + // when + String name = new MockNameImpl("The Hulk", SomeClass.class, true).toString(); // then assertEquals("The Hulk", name); } diff --git a/src/test/java/org/mockito/internal/verification/DescriptionTest.java b/src/test/java/org/mockito/internal/verification/DescriptionTest.java index 5444ca342f..67904cbbbd 100644 --- a/src/test/java/org/mockito/internal/verification/DescriptionTest.java +++ b/src/test/java/org/mockito/internal/verification/DescriptionTest.java @@ -8,7 +8,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; -import static org.mockito.MockitoAnnotations.initMocks; +import static org.mockito.MockitoAnnotations.openMocks; import org.junit.Before; import org.junit.Test; @@ -25,7 +25,7 @@ public class DescriptionTest { @Before public void setUp() { - initMocks(this); + openMocks(this); } /** diff --git a/src/test/java/org/mockito/internal/verification/VerificationOverTimeImplTest.java b/src/test/java/org/mockito/internal/verification/VerificationOverTimeImplTest.java index e8bf2f80be..4200462f45 100644 --- a/src/test/java/org/mockito/internal/verification/VerificationOverTimeImplTest.java +++ b/src/test/java/org/mockito/internal/verification/VerificationOverTimeImplTest.java @@ -7,7 +7,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; -import static org.mockito.MockitoAnnotations.initMocks; +import static org.mockito.MockitoAnnotations.openMocks; import org.junit.Before; import org.junit.Rule; @@ -26,7 +26,7 @@ public class VerificationOverTimeImplTest { @Before public void setUp() { - initMocks(this); + openMocks(this); impl = new VerificationOverTimeImpl(10, 1000, delegate, true); } diff --git a/src/test/java/org/mockito/internal/verification/VerificationWithDescriptionTest.java b/src/test/java/org/mockito/internal/verification/VerificationWithDescriptionTest.java index b188e8ffd2..36c7aacee1 100644 --- a/src/test/java/org/mockito/internal/verification/VerificationWithDescriptionTest.java +++ b/src/test/java/org/mockito/internal/verification/VerificationWithDescriptionTest.java @@ -23,7 +23,7 @@ public class VerificationWithDescriptionTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/org/mockitousage/annotation/AnnotationsTest.java b/src/test/java/org/mockitousage/annotation/AnnotationsTest.java index 8ec83c1523..1a33f933b2 100644 --- a/src/test/java/org/mockitousage/annotation/AnnotationsTest.java +++ b/src/test/java/org/mockitousage/annotation/AnnotationsTest.java @@ -38,7 +38,7 @@ public class AnnotationsTest extends TestBase { @Before public void setup() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test @@ -55,7 +55,7 @@ public void shouldInitMocks() throws Exception { @Test public void shouldScreamWhenInitializingMocksForNullClass() throws Exception { try { - MockitoAnnotations.initMocks(null); + MockitoAnnotations.openMocks(null); fail(); } catch (MockitoException e) { assertEquals( @@ -67,7 +67,7 @@ public void shouldScreamWhenInitializingMocksForNullClass() throws Exception { @Test public void shouldLookForAnnotatedMocksInSuperClasses() throws Exception { Sub sub = new Sub(); - MockitoAnnotations.initMocks(sub); + MockitoAnnotations.openMocks(sub); assertNotNull(sub.getMock()); assertNotNull(sub.getBaseMock()); diff --git a/src/test/java/org/mockitousage/annotation/CaptorAnnotationTest.java b/src/test/java/org/mockitousage/annotation/CaptorAnnotationTest.java index 08480c202d..f015f1b358 100644 --- a/src/test/java/org/mockitousage/annotation/CaptorAnnotationTest.java +++ b/src/test/java/org/mockitousage/annotation/CaptorAnnotationTest.java @@ -43,7 +43,7 @@ public interface MockInterface { @Test public void testNormalUsage() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); // check if assigned correctly assertNotNull(finalCaptor); @@ -70,7 +70,7 @@ public static class WrongType { @Test public void shouldScreamWhenWrongTypeForCaptor() { try { - MockitoAnnotations.initMocks(new WrongType()); + MockitoAnnotations.openMocks(new WrongType()); fail(); } catch (MockitoException e) { } @@ -83,7 +83,7 @@ public static class ToManyAnnotations { @Test public void shouldScreamWhenMoreThanOneMockitoAnnotation() { try { - MockitoAnnotations.initMocks(new ToManyAnnotations()); + MockitoAnnotations.openMocks(new ToManyAnnotations()); fail(); } catch (MockitoException e) { assertThat(e) @@ -95,7 +95,7 @@ public void shouldScreamWhenMoreThanOneMockitoAnnotation() { @Test public void shouldScreamWhenInitializingCaptorsForNullClass() throws Exception { try { - MockitoAnnotations.initMocks(null); + MockitoAnnotations.openMocks(null); fail(); } catch (MockitoException e) { } @@ -104,7 +104,7 @@ public void shouldScreamWhenInitializingCaptorsForNullClass() throws Exception { @Test public void shouldLookForAnnotatedCaptorsInSuperClasses() throws Exception { Sub sub = new Sub(); - MockitoAnnotations.initMocks(sub); + MockitoAnnotations.openMocks(sub); assertNotNull(sub.getCaptor()); assertNotNull(sub.getBaseCaptor()); diff --git a/src/test/java/org/mockitousage/annotation/CaptorAnnotationUnhappyPathTest.java b/src/test/java/org/mockitousage/annotation/CaptorAnnotationUnhappyPathTest.java index ccc6184b67..f49de96cb9 100644 --- a/src/test/java/org/mockitousage/annotation/CaptorAnnotationUnhappyPathTest.java +++ b/src/test/java/org/mockitousage/annotation/CaptorAnnotationUnhappyPathTest.java @@ -30,7 +30,7 @@ public void init() { public void shouldFailIfCaptorHasWrongType() throws Exception { try { // when - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); fail(); } catch (MockitoException e) { // then diff --git a/src/test/java/org/mockitousage/annotation/DeprecatedAnnotationEngineApiTest.java b/src/test/java/org/mockitousage/annotation/DeprecatedAnnotationEngineApiTest.java index eb0bd896c1..a02434cdaf 100644 --- a/src/test/java/org/mockitousage/annotation/DeprecatedAnnotationEngineApiTest.java +++ b/src/test/java/org/mockitousage/annotation/DeprecatedAnnotationEngineApiTest.java @@ -47,7 +47,7 @@ public void shouldInjectMocksIfThereIsNoUserDefinedEngine() throws Exception { SimpleTestCase test = new SimpleTestCase(); // when - MockitoAnnotations.initMocks(test); + MockitoAnnotations.openMocks(test); // then assertNotNull(test.mock); @@ -65,7 +65,7 @@ public void shouldRespectUsersEngine() throws Exception { SimpleTestCase test = new SimpleTestCase(); // when - MockitoAnnotations.initMocks(test); + MockitoAnnotations.openMocks(test); // then assertNotNull(test.mock); diff --git a/src/test/java/org/mockitousage/annotation/MockInjectionUsingConstructorTest.java b/src/test/java/org/mockitousage/annotation/MockInjectionUsingConstructorTest.java index 6d60a321fd..3a89fd3c67 100644 --- a/src/test/java/org/mockitousage/annotation/MockInjectionUsingConstructorTest.java +++ b/src/test/java/org/mockitousage/annotation/MockInjectionUsingConstructorTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; +import static org.mockito.MockitoAnnotations.openMocks; import java.util.AbstractCollection; import java.util.List; @@ -46,7 +46,7 @@ public class MockInjectionUsingConstructorTest { @Before public void before() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test @@ -91,7 +91,7 @@ public void should_report_failure_only_when_object_initialization_throws_excepti throws Exception { try { - MockitoAnnotations.initMocks(new ATest()); + MockitoAnnotations.openMocks(new ATest()); fail(); } catch (MockitoException e) { assertThat(e.getMessage()) @@ -147,7 +147,7 @@ class TestCase { exception.expectMessage( "Cannot instantiate @InjectMocks field named 'f'! Cause: the type 'IMethods' is an interface"); - initMocks(new TestCase()); + openMocks(new TestCase()); } @Test @@ -160,7 +160,7 @@ class TestCase { exception.expectMessage( "Cannot instantiate @InjectMocks field named 'f'! Cause: the type 'TimeUnit' is an enum"); - initMocks(new TestCase()); + openMocks(new TestCase()); } @Test @@ -173,7 +173,7 @@ class TestCase { exception.expectMessage( "Cannot instantiate @InjectMocks field named 'f'! Cause: the type 'AbstractCollection' is an abstract class"); - initMocks(new TestCase()); + openMocks(new TestCase()); } @Test @@ -188,7 +188,7 @@ class InnerClass {} exception.expectMessage( "Cannot instantiate @InjectMocks field named 'f'! Cause: the type 'InnerClass' is an inner non static class"); - initMocks(new TestCase()); + openMocks(new TestCase()); } static class StaticInnerClass {} @@ -200,7 +200,7 @@ class TestCase { } TestCase testClass = new TestCase(); - initMocks(testClass); + openMocks(testClass); assertThat(testClass.f).isInstanceOf(StaticInnerClass.class); } @@ -213,7 +213,7 @@ class TestCase { TestCase testClass = new TestCase(); StaticInnerClass original = testClass.f; - initMocks(testClass); + openMocks(testClass); assertThat(testClass.f).isSameAs(original); } diff --git a/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java b/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java index 88a74a697e..765c4b8d0a 100644 --- a/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java +++ b/src/test/java/org/mockitousage/annotation/MockInjectionUsingSetterOrPropertyTest.java @@ -53,7 +53,7 @@ public class MockInjectionUsingSetterOrPropertyTest extends TestBase { @Before public void enforces_new_instances() { // initMocks called in TestBase Before method, so instances are not the same - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test @@ -81,40 +81,40 @@ public void should_initialize_spy_if_null_and_inject_mocks() { @Test public void should_inject_mocks_if_annotated() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertSame(list, superUnderTest.getAList()); } @Test public void should_not_inject_if_not_annotated() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertNull(superUnderTestWithoutInjection.getAList()); } @Test public void should_inject_mocks_for_class_hierarchy_if_annotated() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertSame(list, baseUnderTest.getAList()); assertSame(map, baseUnderTest.getAMap()); } @Test public void should_inject_mocks_by_name() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertSame(histogram1, subUnderTest.getHistogram1()); assertSame(histogram2, subUnderTest.getHistogram2()); } @Test public void should_inject_spies() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertSame(searchTree, otherBaseUnderTest.getSearchTree()); } @Test public void should_insert_into_field_with_matching_name_when_multiple_fields_of_same_type_exists_in_injectee() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assertNull("not injected, no mock named 'candidate1'", hasTwoFieldsWithSameType.candidate1); assertNotNull( "injected, there's a mock named 'candidate2'", hasTwoFieldsWithSameType.candidate2); @@ -137,7 +137,7 @@ public void should_report_nicely() throws Exception { @InjectMocks ThrowingConstructor failingConstructor; }; try { - MockitoAnnotations.initMocks(failing); + MockitoAnnotations.openMocks(failing); fail(); } catch (MockitoException e) { Assertions.assertThat(e.getMessage()) diff --git a/src/test/java/org/mockitousage/annotation/SpyAnnotationInitializedInBaseClassTest.java b/src/test/java/org/mockitousage/annotation/SpyAnnotationInitializedInBaseClassTest.java index bd3bde79ce..60f87913bf 100644 --- a/src/test/java/org/mockitousage/annotation/SpyAnnotationInitializedInBaseClassTest.java +++ b/src/test/java/org/mockitousage/annotation/SpyAnnotationInitializedInBaseClassTest.java @@ -32,7 +32,7 @@ public void shouldInitSpiesInBaseClass() throws Exception { // given SubClass subClass = new SubClass(); // when - MockitoAnnotations.initMocks(subClass); + MockitoAnnotations.openMocks(subClass); // then assertTrue(MockUtil.isMock(subClass.list)); } @@ -45,7 +45,7 @@ public void init() { @Before public void before() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Spy List spyInBaseclass = new LinkedList(); diff --git a/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java b/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java index 647b7c49bd..8db8ef1d5a 100644 --- a/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java +++ b/src/test/java/org/mockitousage/annotation/SpyAnnotationTest.java @@ -66,7 +66,7 @@ class WithSpy { } WithSpy withSpy = new WithSpy(); - MockitoAnnotations.initMocks(withSpy); + MockitoAnnotations.openMocks(withSpy); when(withSpy.list.size()).thenReturn(3); assertEquals(3, withSpy.list.size()); } @@ -79,7 +79,7 @@ class WithSpy { WithSpy withSpy = new WithSpy(); // when - MockitoAnnotations.initMocks(withSpy); + MockitoAnnotations.openMocks(withSpy); // then verify(withSpy.list, never()).clear(); @@ -92,7 +92,7 @@ class FailingSpy { } try { - MockitoAnnotations.initMocks(new FailingSpy()); + MockitoAnnotations.openMocks(new FailingSpy()); fail(); } catch (MockitoException e) { assertThat(e.getMessage()) @@ -109,7 +109,7 @@ class FailingSpy { } try { - MockitoAnnotations.initMocks(new FailingSpy()); + MockitoAnnotations.openMocks(new FailingSpy()); fail(); } catch (MockitoException e) { assertThat(e.getMessage()).contains("Unable to create mock instance"); @@ -128,7 +128,7 @@ List asSingletonList(String s) { } } SpyAbstractClass withSpy = new SpyAbstractClass(); - MockitoAnnotations.initMocks(withSpy); + MockitoAnnotations.openMocks(withSpy); assertEquals(Arrays.asList("a"), withSpy.asSingletonList("a")); } @@ -157,7 +157,7 @@ String fullStrength() { } } WithMockAndSpy outer = new WithMockAndSpy(); - MockitoAnnotations.initMocks(outer); + MockitoAnnotations.openMocks(outer); when(outer.strength.strength()).thenReturn("strength"); assertEquals("inner strength", outer.strength.fullStrength()); } @@ -176,7 +176,7 @@ class WithSpy { @Spy private Outer.Inner inner; } try { - MockitoAnnotations.initMocks(new WithSpy()); + MockitoAnnotations.openMocks(new WithSpy()); fail(); } catch (MockitoException e) { assertThat(e).hasMessageContaining("@Spy annotation can only initialize inner classes"); @@ -186,7 +186,7 @@ class WithSpy { @Test public void should_report_private_inner_not_supported() throws Exception { try { - MockitoAnnotations.initMocks(new WithInnerPrivate()); + MockitoAnnotations.openMocks(new WithInnerPrivate()); fail(); } catch (MockitoException e) { // Currently fails at instantiation time, because the mock subclass don't have the @@ -201,7 +201,7 @@ public void should_report_private_inner_not_supported() throws Exception { @Test public void should_report_private_abstract_inner_not_supported() throws Exception { try { - MockitoAnnotations.initMocks(new WithInnerPrivateAbstract()); + MockitoAnnotations.openMocks(new WithInnerPrivateAbstract()); fail(); } catch (MockitoException e) { assertThat(e) @@ -217,7 +217,7 @@ public void should_report_private_abstract_inner_not_supported() throws Exceptio @Test public void should_report_private_static_abstract_inner_not_supported() throws Exception { try { - MockitoAnnotations.initMocks(new WithInnerPrivateStaticAbstract()); + MockitoAnnotations.openMocks(new WithInnerPrivateStaticAbstract()); fail(); } catch (MockitoException e) { assertThat(e) diff --git a/src/test/java/org/mockitousage/annotation/WrongSetOfAnnotationsTest.java b/src/test/java/org/mockitousage/annotation/WrongSetOfAnnotationsTest.java index b89c29cae6..f5bf525249 100644 --- a/src/test/java/org/mockitousage/annotation/WrongSetOfAnnotationsTest.java +++ b/src/test/java/org/mockitousage/annotation/WrongSetOfAnnotationsTest.java @@ -18,7 +18,7 @@ public class WrongSetOfAnnotationsTest extends TestBase { @Test(expected = MockitoException.class) public void should_not_allow_Mock_and_Spy() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @Mock @Spy List mock; }); @@ -27,7 +27,7 @@ public void should_not_allow_Mock_and_Spy() throws Exception { @Test public void should_not_allow_Spy_and_InjectMocks_on_interfaces() throws Exception { try { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @InjectMocks @Spy List mock; }); @@ -39,7 +39,7 @@ public void should_not_allow_Spy_and_InjectMocks_on_interfaces() throws Exceptio @Test public void should_allow_Spy_and_InjectMocks() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @InjectMocks @Spy WithDependency mock; }); @@ -51,7 +51,7 @@ static class WithDependency { @Test(expected = MockitoException.class) public void should_not_allow_Mock_and_InjectMocks() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @InjectMocks @Mock List mock; }); @@ -59,7 +59,7 @@ public void should_not_allow_Mock_and_InjectMocks() throws Exception { @Test(expected = MockitoException.class) public void should_not_allow_Captor_and_Mock() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @Mock @Captor ArgumentCaptor captor; }); @@ -67,7 +67,7 @@ public void should_not_allow_Captor_and_Mock() throws Exception { @Test(expected = MockitoException.class) public void should_not_allow_Captor_and_Spy() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @Spy @Captor ArgumentCaptor captor; }); @@ -75,7 +75,7 @@ public void should_not_allow_Captor_and_Spy() throws Exception { @Test(expected = MockitoException.class) public void should_not_allow_Captor_and_InjectMocks() throws Exception { - MockitoAnnotations.initMocks( + MockitoAnnotations.openMocks( new Object() { @InjectMocks @Captor ArgumentCaptor captor; }); diff --git a/src/test/java/org/mockitousage/basicapi/MocksSerializationForAnnotationTest.java b/src/test/java/org/mockitousage/basicapi/MocksSerializationForAnnotationTest.java index c2d1caa35b..749beeb35b 100644 --- a/src/test/java/org/mockitousage/basicapi/MocksSerializationForAnnotationTest.java +++ b/src/test/java/org/mockitousage/basicapi/MocksSerializationForAnnotationTest.java @@ -354,7 +354,7 @@ public static class TestClassThatHoldValidField { should_be_able_to_serialize_type_that_implements_Serializable_but_but_dont_declare_a_no_arg_constructor() throws Exception { TestClassThatHoldValidField testClass = new TestClassThatHoldValidField(); - MockitoAnnotations.initMocks(testClass); + MockitoAnnotations.openMocks(testClass); serializeAndBack(testClass.serializableAndNoDefaultConstructor); } diff --git a/src/test/java/org/mockitousage/bugs/FinalHashCodeAndEqualsRaiseNPEInInitMocksTest.java b/src/test/java/org/mockitousage/bugs/FinalHashCodeAndEqualsRaiseNPEInInitMocksTest.java index 6553b975c8..a2569ccd63 100644 --- a/src/test/java/org/mockitousage/bugs/FinalHashCodeAndEqualsRaiseNPEInInitMocksTest.java +++ b/src/test/java/org/mockitousage/bugs/FinalHashCodeAndEqualsRaiseNPEInInitMocksTest.java @@ -19,7 +19,7 @@ public class FinalHashCodeAndEqualsRaiseNPEInInitMocksTest { @Test public void dont_raise_NullPointerException() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } private static class FieldCharsetHolder { diff --git a/src/test/java/org/mockitousage/bugs/GenericsMockitoAnnotationsTest.java b/src/test/java/org/mockitousage/bugs/GenericsMockitoAnnotationsTest.java index 8f7a18e036..7c3f17ebf2 100644 --- a/src/test/java/org/mockitousage/bugs/GenericsMockitoAnnotationsTest.java +++ b/src/test/java/org/mockitousage/bugs/GenericsMockitoAnnotationsTest.java @@ -5,7 +5,7 @@ package org.mockitousage.bugs; import static org.mockito.BDDMockito.given; -import static org.mockito.MockitoAnnotations.initMocks; +import static org.mockito.MockitoAnnotations.openMocks; import java.util.ArrayList; import java.util.Collection; @@ -37,6 +37,6 @@ , E> T getCollection(T collection) { @Before public void setUp() throws Exception { - initMocks(this); + openMocks(this); } } diff --git a/src/test/java/org/mockitousage/bugs/creation/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java b/src/test/java/org/mockitousage/bugs/creation/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java index 54de41089a..ccd14d9b29 100644 --- a/src/test/java/org/mockitousage/bugs/creation/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java +++ b/src/test/java/org/mockitousage/bugs/creation/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java @@ -20,7 +20,7 @@ public static class WithDumbMethod { @Test public void should_be_able_to_create_spy() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } private static class HasConstructorInvokingMethod { @@ -37,7 +37,7 @@ public static class UsingMethodObjectReferenceResult { @Test public void should_be_able_to_create_spy() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } private static class HasConstructorInvokingMethod { @@ -58,7 +58,7 @@ public static class UsingMethodPrimitiveResult { @Test public void should_be_able_to_create_spy() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } private static class HasConstructorInvokingMethod { diff --git a/src/test/java/org/mockitousage/bugs/injection/ParentTestMockInjectionTest.java b/src/test/java/org/mockitousage/bugs/injection/ParentTestMockInjectionTest.java index cf22301ffc..6c118808aa 100644 --- a/src/test/java/org/mockitousage/bugs/injection/ParentTestMockInjectionTest.java +++ b/src/test/java/org/mockitousage/bugs/injection/ParentTestMockInjectionTest.java @@ -19,7 +19,7 @@ public class ParentTestMockInjectionTest { @Test public void injectMocksShouldInjectMocksFromTestSuperClasses() { ImplicitTest it = new ImplicitTest(); - MockitoAnnotations.initMocks(it); + MockitoAnnotations.openMocks(it); assertNotNull(it.daoFromParent); assertNotNull(it.daoFromSub); @@ -40,7 +40,7 @@ public static class ImplicitTest extends BaseTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/org/mockitoutil/TestBase.java b/src/test/java/org/mockitoutil/TestBase.java index e35ea5b5b0..2fe89504e4 100644 --- a/src/test/java/org/mockitoutil/TestBase.java +++ b/src/test/java/org/mockitoutil/TestBase.java @@ -58,7 +58,7 @@ public void cleanUpConfigInAnyCase() { @Before public void init() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } public static void makeStackTracesClean() { diff --git a/subprojects/android/src/main/java/org/mockito/android/internal/creation/AndroidByteBuddyMockMaker.java b/subprojects/android/src/main/java/org/mockito/android/internal/creation/AndroidByteBuddyMockMaker.java index db77fb7b03..013c283893 100644 --- a/subprojects/android/src/main/java/org/mockito/android/internal/creation/AndroidByteBuddyMockMaker.java +++ b/subprojects/android/src/main/java/org/mockito/android/internal/creation/AndroidByteBuddyMockMaker.java @@ -4,7 +4,9 @@ */ package org.mockito.android.internal.creation; +import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.creation.StaticMockControl; import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; import org.mockito.internal.util.Platform; import org.mockito.invocation.MockHandler; @@ -39,6 +41,15 @@ public T createMock(MockCreationSettings settings, MockHandler handler) { return delegate.createMock(settings, handler); } + @Override + public StaticMockControl createStaticMock(Class type, MockCreationSettings settings, MockHandler handler) { + throw new MockitoException(join( + "The Android mock maker does not support static mocks.", + "", + "Support for static mocks is based on the Instrumentation API which Android does not support." + )); + } + @Override public MockHandler getHandler(Object mock) { return delegate.getHandler(mock); diff --git a/subprojects/errorprone/src/main/java/org/mockito/errorprone/bugpatterns/MockitoAnyClassWithPrimitiveType.java b/subprojects/errorprone/src/main/java/org/mockito/errorprone/bugpatterns/MockitoAnyClassWithPrimitiveType.java index a076f2a0e7..f99c7eeb1b 100644 --- a/subprojects/errorprone/src/main/java/org/mockito/errorprone/bugpatterns/MockitoAnyClassWithPrimitiveType.java +++ b/subprojects/errorprone/src/main/java/org/mockito/errorprone/bugpatterns/MockitoAnyClassWithPrimitiveType.java @@ -12,7 +12,7 @@ import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.matchers.Matchers; -import com.google.errorprone.matchers.method.MethodMatchers.MethodNameMatcher; +import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Type; @@ -42,7 +42,7 @@ public class MockitoAnyClassWithPrimitiveType extends AbstractMockitoAnyForPrimi }; // Match against the any() or any(Class) methods. - private static final MethodNameMatcher GENERIC_ANY = + private static final Matcher GENERIC_ANY = Matchers.staticMethod().onClassAny(CLASS_NAMES).named("any"); @Override diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java new file mode 100644 index 0000000000..2dde8d4a9b --- /dev/null +++ b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2020 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitoinline; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.fail; +import static org.mockito.Mockito.times; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.exceptions.verification.NoInteractionsWanted; +import org.mockito.exceptions.verification.WantedButNotInvoked; + +public final class StaticMockTest { + + @Test + public void testStaticMockSimple() { + assertEquals("foo", Dummy.foo()); + try (MockedStatic ignored = Mockito.mockStatic(Dummy.class)) { + assertNull(Dummy.foo()); + } + assertEquals("foo", Dummy.foo()); + } + + @Test + public void testStaticMockWithVerification() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(Dummy::foo); + } + } + + @Test(expected = WantedButNotInvoked.class) + public void testStaticMockWithVerificationFailed() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.verify(Dummy::foo); + } + } + + @Test + public void testStaticMockWithMoInteractions() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + dummy.verifyNoInteractions(); + } + } + + @Test(expected = NoInteractionsWanted.class) + public void testStaticMockWithMoInteractionsFailed() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verifyNoInteractions(); + } + } + + @Test + public void testStaticMockWithMoMoreInteractions() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(Dummy::foo); + dummy.verifyNoMoreInteractions(); + } + } + + @Test(expected = NoInteractionsWanted.class) + public void testStaticMockWithMoMoreInteractionsFailed() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verifyNoMoreInteractions(); + } + } + + @Test + public void testStaticMockWithDefaultAnswer() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class, invocation -> "bar")) { + assertEquals("bar", Dummy.foo()); + dummy.verify(Dummy::foo); + } + } + + @Test + public void testStaticMockWithRealMethodCall() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenCallRealMethod(); + assertEquals("foo", Dummy.foo()); + dummy.verify(Dummy::foo); + } + } + + @Test + public void testStaticMockReset() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + dummy.reset(); + assertNull(Dummy.foo()); + } + } + + @Test + public void testStaticMockClear() { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.clearInvocations(); + dummy.verifyNoInteractions(); + } + } + + @Test + public void testStaticMockDoesNotAffectDifferentThread() throws InterruptedException { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(Dummy::foo); + AtomicReference reference = new AtomicReference<>(); + Thread thread = new Thread(() -> reference.set(Dummy.foo())); + thread.start(); + thread.join(); + assertEquals("foo", reference.get()); + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(times(2), Dummy::foo); + } + } + + @Test + public void testStaticMockCanCoexistWithMockInDifferentThread() throws InterruptedException { + try (MockedStatic dummy = Mockito.mockStatic(Dummy.class)) { + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(Dummy::foo); + AtomicReference reference = new AtomicReference<>(); + Thread thread = new Thread(() -> { + try (MockedStatic dummy2 = Mockito.mockStatic(Dummy.class)) { + dummy2.when(Dummy::foo).thenReturn("qux"); + reference.set(Dummy.foo()); + } + }); + thread.start(); + thread.join(); + assertEquals("qux", reference.get()); + dummy.when(Dummy::foo).thenReturn("bar"); + assertEquals("bar", Dummy.foo()); + dummy.verify(times(2), Dummy::foo); + } + } + + @Test(expected = MockitoException.class) + public void testStaticMockMustBeExclusiveInScopeWithinThread() { + try ( + MockedStatic dummy = Mockito.mockStatic(Dummy.class); + MockedStatic duplicate = Mockito.mockStatic(Dummy.class) + ) { + fail("Not supposed to allow duplicates"); + } + } + + static class Dummy { + + static String foo() { + return "foo"; + } + } +} diff --git a/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java b/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java index 32609c38b4..c2cf37d5fe 100644 --- a/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java +++ b/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java @@ -4,13 +4,14 @@ */ package org.mockito.junit.jupiter; - import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.Parameter; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -20,6 +21,7 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.internal.configuration.MockAnnotationProcessor; @@ -117,7 +119,7 @@ public class MockitoExtension implements BeforeEachCallback, AfterEachCallback, private final static Namespace MOCKITO = create("org.mockito"); - private final static String SESSION = "session"; + private final static String SESSION = "session", MOCKS = "mocks"; private final Strictness strictness; @@ -150,6 +152,7 @@ public void beforeEach(final ExtensionContext context) { .logger(new MockitoSessionLoggerAdapter(Plugins.getMockitoLogger())) .startMocking(); + context.getStore(MOCKITO).put(MOCKS, new HashSet<>()); context.getStore(MOCKITO).put(SESSION, session); } @@ -176,19 +179,30 @@ private Optional retrieveAnnotationFromTestClasses(final Extens * @param context the current extension context; never {@code null} */ @Override + @SuppressWarnings("unchecked") public void afterEach(ExtensionContext context) { + context.getStore(MOCKITO).remove(MOCKS, Set.class).forEach(mock -> ((MockedStatic) mock).closeOnDemand()); context.getStore(MOCKITO).remove(SESSION, MockitoSession.class) .finishMocking(context.getExecutionException().orElse(null)); } @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException { return parameterContext.isAnnotated(Mock.class); } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + @SuppressWarnings("unchecked") + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException { final Parameter parameter = parameterContext.getParameter(); - return MockAnnotationProcessor.processAnnotationForMock(parameterContext.findAnnotation(Mock.class).get(), parameter.getType(), parameter.getName()); + Object mock = MockAnnotationProcessor.processAnnotationForMock( + parameterContext.findAnnotation(Mock.class).get(), + parameter.getType(), + parameter::getParameterizedType, + parameter.getName()); + if (mock instanceof MockedStatic) { + context.getStore(MOCKITO).get(MOCKS, Set.class).add(mock); + } + return mock; } }