Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.util.test.junitrules; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.rules.ExternalResource; import org.springframework.util.ReflectionUtils; /** * A JUnit rule designed to help with the automatic revert of test objects with mocked fields. * This is intended to be used when writing test code and you wish to set a mock object on a spring singleton bean. * By mocking out fields on spring singletons beans, if you don't remember to revert them to their original values, then * any subsequent tests that expect to see 'proper' services plugged in, may fail when they are instead given a mock. * * <p/> * Example usage: * <pre> * public class YourTestClass * { * // Declare the rule. * @Rule public final TemporaryMockOverride mockOverride = new TemporaryMockOverride(); * * @Test public void aTestMethod() * { * // Get a singleton bean from the spring context * FooService fooService = appContext.getBean("fooService", FooService.class); * * // Create a mocked service (this uses Mockito, but that's not required for this rule to work) * BarService mockedBarService = mock(BarService.class); * * // Don't do this as you're just replacing the original barService: fooService.setBarService(mockedBarService); * // Instead do this: * mockOverride.setTemporaryField(fooService, barService, mockedBarService); * * // Go ahead and use the FooService in test code, whilst relying on a mocked BarService behind it. * // After the rule has completed, the original BarService which spring injected into the FooService will be reset. * } * } * </pre> * * @author Neil Mc Erlean * @since Odin */ public class TemporaryMockOverride extends ExternalResource { private static final Log log = LogFactory.getLog(TemporaryMockOverride.class); private List<FieldValueOverride> pristineFieldValues = new ArrayList<FieldValueOverride>(); @Override protected void before() throws Throwable { // Intentionally empty } @Override protected void after() { // For all objects that have been tampered with, we'll revert them to their original state. for (int i = pristineFieldValues.size() - 1; i >= 0; i--) { FieldValueOverride override = pristineFieldValues.get(i); if (log.isDebugEnabled()) { log.debug("Reverting mocked field '" + override.fieldName + "' on object " + override.objectContainingField + " to original value '" + override.fieldPristineValue + "'"); } // Hack into the Java field object Field f = ReflectionUtils.findField(override.objectContainingField.getClass(), override.fieldName); ReflectionUtils.makeAccessible(f); // and revert its value. ReflectionUtils.setField(f, override.objectContainingField, override.fieldPristineValue); } } public void setTemporaryField(Object objectContainingField, String fieldName, Object fieldValue) { if (log.isDebugEnabled()) { log.debug("Overriding field '" + fieldName + "' on object " + objectContainingField + " to new value '" + fieldValue + "'"); } // Extract the pristine value of the field we're going to mock. Field f = ReflectionUtils.findField(objectContainingField.getClass(), fieldName); if (f == null) { final String msg = "Object of type '" + objectContainingField.getClass().getSimpleName() + "' has no field named '" + fieldName + "'"; if (log.isDebugEnabled()) { log.debug(msg); } throw new IllegalArgumentException(msg); } ReflectionUtils.makeAccessible(f); Object pristineValue = ReflectionUtils.getField(f, objectContainingField); // and add it to the list. pristineFieldValues.add(new FieldValueOverride(objectContainingField, fieldName, pristineValue)); // and set it on the object ReflectionUtils.setField(f, objectContainingField, fieldValue); } private static class FieldValueOverride { public FieldValueOverride(Object objectContainingField, String fieldName, Object pristineValue) { this.objectContainingField = objectContainingField; this.fieldName = fieldName; this.fieldPristineValue = pristineValue; } public final Object objectContainingField; public final String fieldName; public final Object fieldPristineValue; } }