Repository: mockito/mockito
Issue: 2.0.8-beta -> 2.0.9-beta 'Unable to initialize @Spy annotated field
Labels: bug

No test failures when using 2.0.8-beta, but when incrementing to 2.0.9-beta, the following error occurs:

```
org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'monitorTaskScheduler'.
Unable to create mock instance of type 'MonitorTaskScheduler'
    at net.project.dash.monitor.MonitorTaskScheduler$MockitoMock$1184625981.getExecutorService(Unknown Source)
    at net.project.dash.monitor.MonitorTaskScheduler.<init>(MonitorTaskScheduler.java:39)
    at net.project.dash.monitor.MonitorTaskScheduler$MockitoMock$1184625981.<init>(Unknown Source)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.mockito.internal.creation.instance.ConstructorInstantiator.invokeConstructor(ConstructorInstantiator.java:42)
    at org.mockito.internal.creation.instance.ConstructorInstantiator.noArgConstructor(ConstructorInstantiator.java:66)
    at org.mockito.internal.creation.instance.ConstructorInstantiator.newInstance(ConstructorInstantiator.java:17)
    at org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.createMock(ByteBuddyMockMaker.java:27)
    at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33)
    at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59)
    at org.mockito.Mockito.mock(Mockito.java:1284)
    at org.mockito.internal.configuration.SpyAnnotationEngine.newSpyInstance(SpyAnnotationEngine.java:117)
    at org.mockito.internal.configuration.SpyAnnotationEngine.process(SpyAnnotationEngine.java:67)
    at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:73)
    at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:55)
    at org.mockito.MockitoAnnotations.initMocks(MockitoAnnotations.java:108)
    at net.project.dash.monitor.MonitorTaskSchedulerTest.setUp(MonitorTaskSchedulerTest.java:38)
```

Relevant code (MonitorTaskSchedulerTest.java)

``` java
@Spy
MonitorTaskScheduler monitorTaskScheduler;

@Before
public void setUp()
{
    MockitoAnnotations.initMocks(this);
}
```

Relevant code (MonitorTaskScheduler.java)

``` java
public class MonitorTaskScheduler
{
    public MonitorTaskScheduler()
    {
        this.futureToTaskMap = new ConcurrentHashMap<>();
    }
}
```


Issue comments:
Can you try with 2.0.11-beta ?

Same problem with 2.0.11-beta (in fact, I started with that version, and then decremented one by one till the problem was gone).

If I had to make a (somewhat) educated guess I would say it is most likely related to 3b445aa057ada4e64b096634b011eab399f03f56

It's not really that commit, it probably have to do with the change from CGLIB to ByteBuddy. I cannot reproduce locally, can you craft a reproducible test ?

MonitorTaskScheduler.java: 

``` java
public class MonitorTaskScheduler
{
    private final Map<ScheduledFuture<?>, MonitorTask> futureToTaskMap;
    private final ScheduledExecutorService executor;

    public MonitorTaskScheduler()
    {
        futureToTaskMap = new ConcurrentHashMap<>();
        executor = (ScheduledExecutorService) getExecutorService();
    }

    @VisibleForTesting
    ExecutorService getExecutorService()
    {
        return new MonitorTaskExecutor(this);
    }
}
```

MonitorTaskSchedulerTest.java:

``` java
@Spy
MonitorTaskScheduler monitorTaskScheduler;

@Before
public void setUp()
{
    MockitoAnnotations.initMocks(this);
    doReturn(new SynchronousExecutor()).when(monitorTaskScheduler).getExecutorService();
}

@Test
public void test()
{
    monitorTaskScheduler.schedule(() -> { System.out.println("Task") });
}

public class SynchronousExecutor extends AbstractExecutorService
{
    private volatile boolean shutdown;

    public void shutdown()
    {
        shutdown = true;
    }

    public List<Runnable> shutdownNow()
    {
        return null;
    }

    public boolean isShutdown()
    {
        return shutdown;
    }

    public boolean isTerminated()
    {
        return shutdown;
    }

    public boolean awaitTermination(long time, TimeUnit unit) throws InterruptedException
    {
        return true;
    }

    public void execute(Runnable runnable)
    {
        runnable.run();
    }

}
```

I've identified an issue but the given code is incomplete and I don't get the same stacktrace, so it may be another issue.

Here's mine : 

``` java
public class ConstrucotrInvokingMethodShouldWorkTest {
    @Spy HasConstructorInvokingMethod hasConstructorInvokingMethod;

    @Test
    public void should_be_able_to_create_spy() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    private static class HasConstructorInvokingMethod {
        public HasConstructorInvokingMethod() {
            someMethod();
        }
        void someMethod() { }
    }
}
```

In the meantime two workaround, sticks to an older version, or modify the code so it doesn't call methods in the constructor, which may indicate the design is wrong in OOP (builders or factory to the rescue).

Actually there maybe a better workaround, using the `Mockito.spy()` factory method.

``` java
class MonitorTaskSchedulerTest {

// @Spy
MonitorTaskScheduler monitorTaskScheduler = spy(new MinitorTaskScheduler());
```

This even works with `@InjectMocks`

Sorry for not providing a complete example..was trying to copy and paste stuff from difference sources for simplification purposes. Glad you were at least able to identify an issue out of it. I will take your suggestions and do what I can. Thanks.

You're welcome, thanks for testing betas ;)

2d036ec fixed my original issue indeed! Thanks so much, great work.


Pull request review comments:

Patch that resolved the issue:
diff --git a/gradle/javadoc.gradle b/gradle/javadoc.gradle
index c221c6277b..716fb6a102 100644
--- a/gradle/javadoc.gradle
+++ b/gradle/javadoc.gradle
@@ -35,8 +35,8 @@ task mockitoJavadoc(type: Javadoc) {
         <script type="text/javascript">
           var usingOldIE = \$.browser.msie && parseInt(\$.browser.version) < 9;
           if(!usingOldIE) {
-              \$("head").append("<link rel=\\"shortcut icon\\" href=\\"@{docRoot}/favicon.ico?v=cafebabe\\">")
-              \$("head", window.parent.document).append("<link rel=\\"shortcut icon\\" href=\\"@{docRoot}/favicon.ico?v=cafebabe\\">")
+              \$("head").append("<link rel=\\"shortcut icon\\" href=\\"{@docRoot}/favicon.ico?v=cafebabe\\">")
+              \$("head", window.parent.document).append("<link rel=\\"shortcut icon\\" href=\\"{@docRoot}/favicon.ico?v=cafebabe\\">")
               hljs.initHighlightingOnLoad();
               injectProjectVersionForJavadocJDK6("Mockito ${project.version} API",
                                                  "em#mockito-version-header-javadoc7-header",
diff --git a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
index 1ec76d2c6f..2875df319d 100644
--- a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
+++ b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
@@ -1,8 +1,8 @@
 package org.mockito.internal.creation.bytebuddy;
 
-import static org.mockito.internal.util.StringJoiner.join;
 import org.mockito.exceptions.base.MockitoException;
 import org.mockito.internal.InternalMockHandler;
+import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.MockAccess;
 import org.mockito.internal.creation.instance.Instantiator;
 import org.mockito.internal.creation.instance.InstantiatorProvider;
 import org.mockito.invocation.MockHandler;
@@ -10,6 +10,8 @@
 import org.mockito.mock.SerializableMode;
 import org.mockito.plugins.MockMaker;
 
+import static org.mockito.internal.util.StringJoiner.join;
+
 public class ByteBuddyMockMaker implements MockMaker {
 
     private final CachingMockBytecodeGenerator cachingMockBytecodeGenerator;
@@ -25,7 +27,7 @@ public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
         T mockInstance = null;
         try {
             mockInstance = instantiator.newInstance(mockedProxyType);
-            MockMethodInterceptor.MockAccess mockAccess = (MockMethodInterceptor.MockAccess) mockInstance;
+            MockAccess mockAccess = (MockAccess) mockInstance;
             mockAccess.setMockitoInterceptor(new MockMethodInterceptor(asInternalMockHandler(handler), settings));
 
             return ensureMockIsAssignableToMockedType(settings, mockInstance);
@@ -75,14 +77,14 @@ private static String describeClass(Object instance) {
     }
 
     public MockHandler getHandler(Object mock) {
-        if (!(mock instanceof MockMethodInterceptor.MockAccess)) {
+        if (!(mock instanceof MockAccess)) {
             return null;
         }
-        return ((MockMethodInterceptor.MockAccess) mock).getMockitoInterceptor().getMockHandler();
+        return ((MockAccess) mock).getMockitoInterceptor().getMockHandler();
     }
 
     public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
-        ((MockMethodInterceptor.MockAccess) mock).setMockitoInterceptor(
+        ((MockAccess) mock).setMockitoInterceptor(
                 new MockMethodInterceptor(asInternalMockHandler(newHandler), settings)
         );
     }
diff --git a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockBytecodeGenerator.java b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockBytecodeGenerator.java
index 8d7c42d252..e4ae2bf8f9 100644
--- a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockBytecodeGenerator.java
+++ b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockBytecodeGenerator.java
@@ -5,9 +5,17 @@
 import net.bytebuddy.dynamic.DynamicType;
 import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
 import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.FieldAccessor;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
 import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
+import net.bytebuddy.implementation.bind.annotation.FieldProxy;
 import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock;
+import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod;
+import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod.FieldGetter;
+import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod.FieldSetter;
+import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.MockAccess;
 import org.mockito.internal.creation.util.SearchingClassLoader;
 
 import java.util.Random;
@@ -15,21 +23,25 @@
 import static net.bytebuddy.description.modifier.FieldManifestation.FINAL;
 import static net.bytebuddy.description.modifier.Ownership.STATIC;
 import static net.bytebuddy.description.modifier.Visibility.PRIVATE;
-import static net.bytebuddy.implementation.FieldAccessor.ofBeanProperty;
 import static net.bytebuddy.implementation.MethodDelegation.to;
-import static net.bytebuddy.implementation.MethodDelegation.toInstanceField;
-import static net.bytebuddy.matcher.ElementMatchers.*;
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.isEquals;
+import static net.bytebuddy.matcher.ElementMatchers.isHashCode;
 
 class MockBytecodeGenerator {
     private final ByteBuddy byteBuddy;
     private final Random random;
+    private final Implementation toDispatcher;
 
     public MockBytecodeGenerator() {
         byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V5)
-//                .withIgnoredMethods(isBridge())
                 .withDefaultMethodAttributeAppender(MethodAttributeAppender.ForInstrumentedMethod.INSTANCE)
                 .withAttribute(TypeAttributeAppender.ForSuperType.INSTANCE);
 
+        // Necessary for ConstructorInstantiator
+        toDispatcher = MethodDelegation.to(DispatcherDefaultingToRealMethod.class)
+                                       .appendParameterBinder(FieldProxy.Binder.install(FieldGetter.class,
+                                                                                        FieldSetter.class));
         random = new Random();
     }
 
@@ -38,9 +50,9 @@ public <T> Class<? extends T> generateMockClass(MockFeatures<T> features) {
                 byteBuddy.subclass(features.mockedType, ConstructorStrategy.Default.IMITATE_SUPER_TYPE)
                          .name(nameFor(features.mockedType))
                          .implement(features.interfaces.toArray(new Class<?>[features.interfaces.size()]))
-                         .method(any()).intercept(toInstanceField(MockMethodInterceptor.class, "mockitoInterceptor")
-                                                          .filter(isDeclaredBy(MockMethodInterceptor.class)))
-                         .implement(MockMethodInterceptor.MockAccess.class).intercept(ofBeanProperty())
+                         .method(any()).intercept(toDispatcher)
+                         .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
+                         .implement(MockAccess.class).intercept(FieldAccessor.ofBeanProperty())
                          .method(isHashCode()).intercept(to(MockMethodInterceptor.ForHashCode.class))
                          .method(isEquals()).intercept(to(MockMethodInterceptor.ForEquals.class))
                          .defineField("serialVersionUID", long.class, STATIC, PRIVATE, FINAL).value(42L);
@@ -48,18 +60,19 @@ public <T> Class<? extends T> generateMockClass(MockFeatures<T> features) {
             builder = builder.implement(CrossClassLoaderSerializableMock.class)
                              .intercept(to(MockMethodInterceptor.ForWriteReplace.class));
         }
-        Class<?>[] allMockedTypes = new Class<?>[features.interfaces.size() + 1];
-        allMockedTypes[0] = features.mockedType;
-//            System.arraycopy(interfaces.toArray(), 0, allMockedTypes, 1, interfaces.size());
-        int index = 1;
-        for (Class<?> type : features.interfaces) {
-            allMockedTypes[index++] = type;
-        }
         return builder.make()
-                      .load(SearchingClassLoader.combineLoadersOf(allMockedTypes), ClassLoadingStrategy.Default.INJECTION)
+                      .load(SearchingClassLoader.combineLoadersOf(allMockedTypes(features)), ClassLoadingStrategy.Default.INJECTION)
                       .getLoaded();
     }
 
+    private <T> Class<?>[] allMockedTypes(MockFeatures<T> features) {
+        Class<?>[] allMockedTypes = new Class<?>[features.interfaces.size() + 1];
+        allMockedTypes[0] = features.mockedType;
+        System.arraycopy(features.interfaces.toArray(), 0,
+                         (Object[]) allMockedTypes, 1, features.interfaces.size());
+        return allMockedTypes;
+    }
+
     // TODO inspect naming strategy (for OSGI, signed package, java.* (and bootstrap classes), etc...)
     private String nameFor(Class<?> type) {
         String typeName = type.getName();
diff --git a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
index c70d849dc3..3e55e6cb0d 100644
--- a/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
+++ b/mockmaker/bytebuddy/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java
@@ -1,9 +1,6 @@
 package org.mockito.internal.creation.bytebuddy;
 
-import java.io.ObjectStreamException;
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.concurrent.Callable;
+import net.bytebuddy.implementation.bind.annotation.*;
 import org.mockito.internal.InternalMockHandler;
 import org.mockito.internal.creation.DelegatingMethod;
 import org.mockito.internal.invocation.MockitoMethod;
@@ -11,14 +8,11 @@
 import org.mockito.internal.progress.SequenceNumber;
 import org.mockito.invocation.MockHandler;
 import org.mockito.mock.MockCreationSettings;
-import net.bytebuddy.implementation.bind.annotation.AllArguments;
-import net.bytebuddy.implementation.bind.annotation.Argument;
-import net.bytebuddy.implementation.bind.annotation.BindingPriority;
-import net.bytebuddy.implementation.bind.annotation.DefaultCall;
-import net.bytebuddy.implementation.bind.annotation.Origin;
-import net.bytebuddy.implementation.bind.annotation.RuntimeType;
-import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.implementation.bind.annotation.This;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
 
 public class MockMethodInterceptor implements Serializable {
 
@@ -122,8 +116,53 @@ public static Object doWriteReplace(@This MockAccess thiz) throws ObjectStreamEx
         }
     }
 
-    public static interface MockAccess {
+    public interface MockAccess {
         MockMethodInterceptor getMockitoInterceptor();
         void setMockitoInterceptor(MockMethodInterceptor mockMethodInterceptor);
     }
+
+    public static class DispatcherDefaultingToRealMethod {
+        public interface FieldGetter<T> {
+            T getValue();
+        }
+        public interface FieldSetter<T> {
+            void setValue(T value);
+        }
+
+        @RuntimeType
+        @BindingPriority(BindingPriority.DEFAULT * 2)
+        public static Object interceptSuperCallable(@This Object mock,
+                                                    @FieldProxy("mockitoInterceptor") FieldGetter<MockMethodInterceptor> fieldGetter,
+                                                    @Origin Method invokedMethod,
+                                                    @AllArguments Object[] arguments,
+                                                    @SuperCall(serializableProxy = true) Callable<?> superCall) throws Throwable {
+            MockMethodInterceptor interceptor = fieldGetter.getValue();
+            if (interceptor == null) {
+                return superCall.call();
+            }
+            return interceptor.doIntercept(
+                    mock,
+                    invokedMethod,
+                    arguments,
+                    new InterceptedInvocation.SuperMethod.FromCallable(superCall)
+            );
+        }
+
+        @RuntimeType
+        public static Object interceptAbstract(@This Object mock,
+                                               @FieldProxy("mockitoInterceptor") FieldGetter<MockMethodInterceptor> fieldGetter,
+                                               @Origin(cache = true) Method invokedMethod,
+                                               @AllArguments Object[] arguments) throws Throwable {
+            MockMethodInterceptor interceptor = fieldGetter.getValue();
+            if (interceptor == null) {
+                return null;
+            }
+            return interceptor.doIntercept(
+                    mock,
+                    invokedMethod,
+                    arguments,
+                    InterceptedInvocation.SuperMethod.IsIllegal.INSTANCE
+            );
+        }
+    }
 }


Test patch:
diff --git a/test/org/mockitousage/bugs/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java b/test/org/mockitousage/bugs/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java
new file mode 100644
index 0000000000..af85e88f15
--- /dev/null
+++ b/test/org/mockitousage/bugs/ConstructorInvokingMethodShouldNotRaiseExceptionTest.java
@@ -0,0 +1,46 @@
+package org.mockitousage.bugs;
+
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+@RunWith(Enclosed.class)
+public class ConstructorInvokingMethodShouldNotRaiseExceptionTest {
+
+    public static class WithDumbMethod {
+        @Spy
+        HasConstructorInvokingMethod hasConstructorInvokingMethod;
+
+        @Test
+        public void should_be_able_to_create_spy() throws Exception {
+            MockitoAnnotations.initMocks(this);
+        }
+
+        private static class HasConstructorInvokingMethod {
+            public HasConstructorInvokingMethod() { someMethod(); }
+
+            void someMethod() { }
+        }
+    }
+
+    public static class UsingMethodResult {
+        @Spy
+        HasConstructorInvokingMethod hasConstructorInvokingMethod;
+
+        @Test
+        public void should_be_able_to_create_spy() throws Exception {
+            MockitoAnnotations.initMocks(this);
+        }
+
+        private static class HasConstructorInvokingMethod {
+            private final boolean doesIt;
+            public HasConstructorInvokingMethod() {
+                doesIt = someMethod().contains("yup");
+            }
+
+            String someMethod() { return "tada!"; }
+        }
+    }
+}