• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockito.internal.configuration;
6 
7 import java.lang.annotation.Annotation;
8 import java.lang.reflect.Constructor;
9 import java.lang.reflect.Field;
10 import java.lang.reflect.InvocationTargetException;
11 import java.lang.reflect.Modifier;
12 import org.mockito.Captor;
13 import org.mockito.InjectMocks;
14 import org.mockito.Mock;
15 import org.mockito.MockSettings;
16 import org.mockito.Mockito;
17 import org.mockito.Spy;
18 import org.mockito.exceptions.base.MockitoException;
19 import org.mockito.internal.util.MockUtil;
20 import org.mockito.plugins.AnnotationEngine;
21 
22 import static org.mockito.Mockito.CALLS_REAL_METHODS;
23 import static org.mockito.Mockito.withSettings;
24 import static org.mockito.internal.exceptions.Reporter.unsupportedCombinationOfAnnotations;
25 import static org.mockito.internal.util.StringUtil.join;
26 
27 /**
28  * Process fields annotated with @Spy.
29  * <p/>
30  * <p>
31  * Will try transform the field in a spy as with <code>Mockito.spy()</code>.
32  * </p>
33  * <p/>
34  * <p>
35  * If the field is not initialized, will try to initialize it, with a no-arg constructor.
36  * </p>
37  * <p/>
38  * <p>
39  * If the field is also annotated with the <strong>compatible</strong> &#64;InjectMocks then the field will be ignored,
40  * The injection engine will handle this specific case.
41  * </p>
42  * <p/>
43  * <p>This engine will fail, if the field is also annotated with incompatible Mockito annotations.
44  */
45 @SuppressWarnings({"unchecked"})
46 public class SpyAnnotationEngine implements AnnotationEngine, org.mockito.configuration.AnnotationEngine {
47 
48     @Override
process(Class<?> context, Object testInstance)49     public void process(Class<?> context, Object testInstance) {
50         Field[] fields = context.getDeclaredFields();
51         for (Field field : fields) {
52             if (field.isAnnotationPresent(Spy.class) && !field.isAnnotationPresent(InjectMocks.class)) {
53                 assertNoIncompatibleAnnotations(Spy.class, field, Mock.class, Captor.class);
54                 field.setAccessible(true);
55                 Object instance;
56                 try {
57                     instance = field.get(testInstance);
58                     if (MockUtil.isMock(instance)) {
59                         // instance has been spied earlier
60                         // for example happens when MockitoAnnotations.initMocks is called two times.
61                         Mockito.reset(instance);
62                     } else if (instance != null) {
63                         field.set(testInstance, spyInstance(field, instance));
64                     } else {
65                         field.set(testInstance, spyNewInstance(testInstance, field));
66                     }
67                 } catch (Exception e) {
68                     throw new MockitoException("Unable to initialize @Spy annotated field '" + field.getName() + "'.\n" + e.getMessage(), e);
69                 }
70             }
71         }
72     }
73 
spyInstance(Field field, Object instance)74     private static Object spyInstance(Field field, Object instance) {
75         return Mockito.mock(instance.getClass(),
76                             withSettings().spiedInstance(instance)
77                                                            .defaultAnswer(CALLS_REAL_METHODS)
78                                                            .name(field.getName()));
79     }
80 
spyNewInstance(Object testInstance, Field field)81     private static Object spyNewInstance(Object testInstance, Field field)
82             throws InstantiationException, IllegalAccessException, InvocationTargetException {
83         MockSettings settings = withSettings().defaultAnswer(CALLS_REAL_METHODS)
84                                               .name(field.getName());
85         Class<?> type = field.getType();
86         if (type.isInterface()) {
87             return Mockito.mock(type, settings.useConstructor());
88         }
89         int modifiers = type.getModifiers();
90         if (typeIsPrivateAbstractInnerClass(type, modifiers)) {
91             throw new MockitoException(join("@Spy annotation can't initialize private abstract inner classes.",
92                                             "  inner class: '" + type.getSimpleName() + "'",
93                                             "  outer class: '" + type.getEnclosingClass().getSimpleName() + "'",
94                                             "",
95                                             "You should augment the visibility of this inner class"));
96         }
97         if (typeIsNonStaticInnerClass(type, modifiers)) {
98             Class<?> enclosing = type.getEnclosingClass();
99             if (!enclosing.isInstance(testInstance)) {
100                 throw new MockitoException(join("@Spy annotation can only initialize inner classes declared in the test.",
101                                                 "  inner class: '" + type.getSimpleName() + "'",
102                                                 "  outer class: '" + enclosing.getSimpleName() + "'",
103                                                 ""));
104             }
105             return Mockito.mock(type, settings.useConstructor()
106                                               .outerInstance(testInstance));
107         }
108 
109         Constructor<?> constructor = noArgConstructorOf(type);
110         if (Modifier.isPrivate(constructor.getModifiers())) {
111             constructor.setAccessible(true);
112             return Mockito.mock(type, settings.spiedInstance(constructor.newInstance()));
113         } else {
114             return Mockito.mock(type, settings.useConstructor());
115         }
116     }
117 
noArgConstructorOf(Class<?> type)118     private static Constructor<?> noArgConstructorOf(Class<?> type) {
119         Constructor<?> constructor;
120         try {
121             constructor = type.getDeclaredConstructor();
122         } catch (NoSuchMethodException e) {
123             throw new MockitoException("Please ensure that the type '" + type.getSimpleName() + "' has a no-arg constructor.");
124         }
125         return constructor;
126     }
127 
typeIsNonStaticInnerClass(Class<?> type, int modifiers)128     private static boolean typeIsNonStaticInnerClass(Class<?> type, int modifiers) {
129         return !Modifier.isStatic(modifiers) && type.getEnclosingClass() != null;
130     }
131 
typeIsPrivateAbstractInnerClass(Class<?> type, int modifiers)132     private static boolean typeIsPrivateAbstractInnerClass(Class<?> type, int modifiers) {
133         return Modifier.isPrivate(modifiers) && Modifier.isAbstract(modifiers) && type.getEnclosingClass() != null;
134     }
135 
136     //TODO duplicated elsewhere
assertNoIncompatibleAnnotations(Class<? extends Annotation> annotation, Field field, Class<? extends Annotation>... undesiredAnnotations)137     private static void assertNoIncompatibleAnnotations(Class<? extends Annotation> annotation,
138                                                         Field field,
139                                                         Class<? extends Annotation>... undesiredAnnotations) {
140         for (Class<? extends Annotation> u : undesiredAnnotations) {
141             if (field.isAnnotationPresent(u)) {
142                 throw unsupportedCombinationOfAnnotations(annotation.getSimpleName(),
143                                                           annotation.getClass().getSimpleName());
144             }
145         }
146     }
147 }
148