package rocks.inspectit.shared.all.testbase; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; /** * Base class for all testing purposes in inspectIT. * * Automatically ensures that each testee (annotated with @InjectMock) is reset between each test * method invocation. * * @author Stefan Siegl */ public abstract class TestBase { /** The fields annotated @InjectMocks that should be reset after each method invocation. */ List<String> fieldsToReset = new ArrayList<String>(); /** * The method will be run before the first test method in the current class is invoked. * <ul> * <li>Scans the Unit Test class for @InjectMock annotations to reset them after each method * invocation.</li> * </ul> */ @BeforeClass public void baseBeforeClass() { scanForAnnotations(); } /** * The method will be run before each test method. * <ul> * <li>Initializes any {@link Mock} that is present in the Unit Test.</li> * <li>This also injects the mocks into the testee class if it is annotated using the * <code>@InjectMocks</code> annotation.</li> * </ul> */ @BeforeMethod public void baseBeforeMethod() { MockitoAnnotations.initMocks(this); } /** * The method will be run after each test method. * <ul> * <li>It checks for correct mockito usage in each test run.</li> * <li>It resets the field annotated with @InjectMocks.</li> * </ul> */ @AfterMethod public void baseAfterMethod() { Mockito.validateMockitoUsage(); for (String field : this.fieldsToReset) { setInstanceValue(this, field, null); } } /** * This method is used to scan for annotations of type {@link InjectMocks} and adding it to the * list of {@link #fieldsToReset}. */ private void scanForAnnotations() { Field[] allFields = getAllFieldsForClass(this.getClass()); for (Field field : allFields) { Annotation[] annotations = field.getAnnotations(); for (Annotation annotation : annotations) { if (annotation.annotationType().equals(InjectMocks.class)) { this.fieldsToReset.add(field.getName()); } } } } /** get the list of all fields of class and all super-classes of them */ private Field[] getAllFieldsForClass(Class<?> clazz) { List<Field> fields = new ArrayList<Field>(); do { fields.addAll(Arrays.asList(clazz.getDeclaredFields())); clazz = clazz.getSuperclass(); } while (clazz != Object.class); return fields.toArray(new Field[fields.size()]); } /** * Use reflection to change value of any instance field. * * @param objectToChange * An Object instance. * @param fieldName * The name of a field in the class instantiated by classInstancee * @param newValue * The value you want the field to be set to. * @throws RuntimeException * when setting the value for the field failed. */ public void setInstanceValue(Object objectToChange, String fieldName, Object newValue) { String targetClass = null; try { // Get the private field Field field; if (objectToChange instanceof Class<?>) { targetClass = ((Class<?>) objectToChange).getName(); field = findField((Class<?>) objectToChange, fieldName); } else { targetClass = objectToChange.getClass().getName(); field = findField(objectToChange.getClass(), fieldName); } // Allow modification on the field field.setAccessible(true); // Sets the field to the new value for this instance field.set(objectToChange, newValue); } catch (Exception e) { throw new RuntimeException("Error setting field [" + fieldName + "] in class [" + targetClass + "]", e); // NOPMD } } private Field findField(Class<?> clazz, String fieldName) throws NoSuchFieldException { if (clazz == null) { throw new NoSuchFieldException("Field could not be found in the class (and superclasses)."); } Field field = null; try { field = clazz.getDeclaredField(fieldName); } catch (Exception e) { // NOPMD intended, as we go up the superclasses // NOCHECKSTYLE } if (field != null) { return field; } else { return findField(clazz.getSuperclass(), fieldName); } } }