mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
Adding a new JUnit rule to help with cleaning up spring singleton beans whose backend services have been mocked out.
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@39395 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012
|
||||
Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.alfresco.util.test.junitrules;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@link TemporaryMockOverride}.
|
||||
*
|
||||
* @author Neil McErlean
|
||||
* @since Odin
|
||||
*/
|
||||
public class TemporaryMockOverrideTest
|
||||
{
|
||||
private static final String REAL_DATA = "Hello";
|
||||
private static final String MOCKED_DATA = "--";
|
||||
|
||||
private final FooService realFooService = new FooServiceImpl();
|
||||
private final BarService realBarService = new BarServiceImpl();
|
||||
private final AbcService realAbcService = new AbcServiceImpl();
|
||||
|
||||
@Before public void init()
|
||||
{
|
||||
FooServiceImpl fooServiceImpl = (FooServiceImpl)realFooService;
|
||||
fooServiceImpl.setBarService(realBarService);
|
||||
fooServiceImpl.setAbcService(realAbcService);
|
||||
}
|
||||
|
||||
@Test public void mockFieldsWithinServiceAndThenEnsureTheProperFieldValuesAreRestoredAfterCleanup() throws Throwable
|
||||
{
|
||||
TemporaryMockOverride mockRule = new TemporaryMockOverride();
|
||||
mockRule.before();
|
||||
|
||||
assertEquals("Original BarService giving wrong data.", REAL_DATA, realFooService.getBarService().getString());
|
||||
assertEquals("Original AbcService giving wrong data.", REAL_DATA, realFooService.getAbcService().getString());
|
||||
|
||||
BarService mockedBarService = mock(BarService.class);
|
||||
when(mockedBarService.getString()).thenReturn(MOCKED_DATA);
|
||||
|
||||
AbcService mockedAbcService = mock(AbcService.class);
|
||||
when(mockedAbcService.getString()).thenReturn(MOCKED_DATA);
|
||||
|
||||
FooServiceImpl fooServiceWithMockedServices = new FooServiceImpl();
|
||||
// We'll start it off with the 'correct' values
|
||||
fooServiceWithMockedServices.setBarService(realBarService);
|
||||
fooServiceWithMockedServices.setAbcService(realAbcService);
|
||||
|
||||
// ...and then set the mocked values via the rule, which will remember the old values and revert them for us automatically.
|
||||
mockRule.setTemporaryField(fooServiceWithMockedServices, "barService", mockedBarService);
|
||||
mockRule.setTemporaryField(fooServiceWithMockedServices, "abcService", mockedAbcService);
|
||||
|
||||
|
||||
assertEquals("Mocked BarService giving wrong data.", MOCKED_DATA, fooServiceWithMockedServices.getBarService().getString());
|
||||
assertEquals("Mocked AbcService giving wrong data.", MOCKED_DATA, fooServiceWithMockedServices.getAbcService().getString());
|
||||
|
||||
mockRule.after();
|
||||
|
||||
// Now it should all be magically reverted.
|
||||
assertEquals("BarService giving wrong data.", REAL_DATA, fooServiceWithMockedServices.getBarService().getString());
|
||||
assertEquals("AbcService giving wrong data.", REAL_DATA, fooServiceWithMockedServices.getAbcService().getString());
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class) public void mockNonExistentFieldsWithinService() throws Throwable
|
||||
{
|
||||
TemporaryMockOverride mockRule = new TemporaryMockOverride();
|
||||
mockRule.before();
|
||||
|
||||
assertEquals("Original BarService giving wrong data.", REAL_DATA, realFooService.getBarService().getString());
|
||||
|
||||
BarService mockedBarService = mock(BarService.class);
|
||||
when(mockedBarService.getString()).thenReturn(MOCKED_DATA);
|
||||
|
||||
FooServiceImpl fooServiceWithMockedServices = new FooServiceImpl();
|
||||
// We'll start it off with the 'correct' values
|
||||
fooServiceWithMockedServices.setBarService(realBarService);
|
||||
|
||||
// ...and then set an illegal mocked value via the rule.
|
||||
mockRule.setTemporaryField(fooServiceWithMockedServices, "noSuchService", mockedBarService);
|
||||
|
||||
|
||||
assertEquals("Mocked BarService giving wrong data.", MOCKED_DATA, fooServiceWithMockedServices.getBarService().getString());
|
||||
|
||||
mockRule.after();
|
||||
|
||||
// Now it should all be magically reverted.
|
||||
assertEquals("BarService giving wrong data.", REAL_DATA, fooServiceWithMockedServices.getBarService().getString());
|
||||
}
|
||||
|
||||
public interface FooService { public BarService getBarService(); public AbcService getAbcService(); }
|
||||
|
||||
public class FooServiceImpl implements FooService
|
||||
{
|
||||
private BarService barService;
|
||||
private AbcService abcService;
|
||||
|
||||
public BarService getBarService() { return this.barService; }
|
||||
public void setBarService(BarService barService) { this.barService = barService; }
|
||||
public AbcService getAbcService() { return this.abcService; }
|
||||
public void setAbcService(AbcService abcService) { this.abcService = abcService; }
|
||||
}
|
||||
|
||||
public interface BarService { public String getString(); }
|
||||
|
||||
public class BarServiceImpl implements BarService { public String getString() { return REAL_DATA; } }
|
||||
|
||||
public interface AbcService { public String getString(); }
|
||||
|
||||
public class AbcServiceImpl implements AbcService { public String getString() { return REAL_DATA; } }
|
||||
}
|
||||
|
Reference in New Issue
Block a user