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> @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 u.getSimpleName()); 144 } 145 } 146 } 147 } 148