Home | History | Annotate | Download | only in configuration
      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
     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 
     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 
     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 
    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 
    128     private static boolean typeIsNonStaticInnerClass(Class<?> type, int modifiers) {
    129         return !Modifier.isStatic(modifiers) && type.getEnclosingClass() != null;
    130     }
    131 
    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
    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