#java #unit-testing #proxy #mockito #delegation
#java #модульное тестирование #прокси #mockito #делегирование
Вопрос:
Могу ли я автоматически делегировать все вызовы методам другого экземпляра, которые используют тот же интерфейс?
У меня есть большой класс (я хочу протестировать), подобный этому:
class MyClassUnderTest implements SomeInterface {
public void anImportantMethod() {
}
@Override
public void fromTheInterface() {
}
@Override
public void fromTheInterface2() {
}
private void utilFunc() {}
}
Он реализует интерфейс
interface SomeInterface {
void fromTheInterface();
void fromTheInterface2();
}
Учитывая это, во время unittest я хочу «скрыть» методы, унаследованные от интерфейса с различными реализациями. Для этого я написал служебный класс
class DebugSomeInterface implements SomeInterface {
@Override public void fromTheInterface() { log.debug("1"); }
@Override public void fromTheInterface2() { log.debug("2"); };
}
Теперь мне нужно «делегировать» все возможные вызовы для этой реализации. Я делаю это вручную, производя из MyClassUnderTest
моего собственного класса, делегируя все вызовы этому:
class MyClassUnderTest_Mock extends MyClassUnderTest {
DebugSomeInterface delegated = new DebugSomeInterface();
@Override public void fromTheInterface() {
delegated.fromTheInterface();
}
@Override public void fromTheInterface2() {
delegated.fromTheInterface2();
};
Только у меня есть много реализующих классов SomeInterface
, и выполнение этого вручную утомительно и подвержено ошибкам.
Я хотел бы (наполовину) автоматизированный способ создания MyClassUnderTest_Mock
экземпляров, как mock(...)
в Mockito. Может быть, что-то вроде
MyClassUnderTest underTest = new MyClassUnderTest();
DebugSomeInterface delegated = new DebugSomeInterface();
MyClassUnderTest instance = mixin(underTest, SomeInterface.class, delegated);
Возможно, это привело бы к созданию «прокси», instance
который делегирует все вызовы методов от SomeInterface
к delegated
, а остальные к underTest
.
В ядре Java есть некоторый механизм с прокси-объектами, но я не могу собрать все это вместе.
Комментарии:
1. Вы могли бы шпионить (new MyClassUnderTest()), а затем имитировать методы, для которых вы хотите обеспечить поведение
Ответ №1:
Вы можете сделать что-то вроде этого:
public static <T, U extends T> U createProxy(U classUnderTest, Class<T> interfaceType, T debugImplementation)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
U spy = Mockito.spy(classUnderTest);
for (Method m : interfaceType.getMethods()) {
Object[] params = new Object[m.getParameterTypes().length];
for (int i = 0; i < params.length; i ) {
params[i] = Mockito.any();
}
Mockito.when(m.invoke(spy, params)).thenAnswer(new Delegate(debugImplementation, m));
}
return spy;
}
public static class Delegate implements Answer {
private final Object delegate;
private final Method delegateMethod;
public Delegate(Object delegate, Method delegateMethod) {
this.delegate = delegate;
this.delegateMethod = delegateMethod;
}
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return delegateMethod.invoke(delegate, invocation.getArguments());
}
}
А затем используйте его как:
MyClassUnderTest underTest = new MyClassUnderTest();
DebugSomeInterface delegated = new DebugSomeInterface();
MyClassUnderTest instance = createProxy(underTest, SomeInterface.class, delegated);
Это будет работать так, как вы хотите, но в качестве отказа от ответственности я думаю, что это плохая идея… Необходимость шпионить за тестируемым объектом и извлекать фрагменты тестируемого объекта должна быть красным флагом… Это плохая практика, вам нужно делать подобные вещи только тогда, когда сложность кода выше оптимальной и части вашего класса должны быть разделены на другие классы. Я никогда не сталкивался с подобной ситуацией, когда рефакторинг моей логики не был лучшим решением…