Использование Mockito для тестирования абстрактных классов
Я бы хотел протестировать абстрактный класс. Конечно, я могу вручную написать макет, который наследуется от класса.
Могу ли я сделать это, используя насмешливую фреймворк (я использую Mockito) вместо того, чтобы рисовать мой макет? Как?
Ответы
Ответ 1
Следующее предложение позволяет тестировать абстрактные классы без создания "реального" подкласса - Mock является подклассом.
используйте Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)
, затем издеваются над любыми абстрактными методами, которые вызывают.
Пример:
public abstract class My {
public Result methodUnderTest() { ... }
protected abstract void methodIDontCareAbout();
}
public class MyTest {
@Test
public void shouldFailOnNullIdentifiers() {
My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
Assert.assertSomething(my.methodUnderTest());
}
}
Примечание. Красота этого решения заключается в том, что вам не нужно реализовывать абстрактные методы, если они никогда не вызываются.
По моему честному мнению, это более аккуратно, чем использование шпиона, поскольку шпион требует экземпляра, а это значит, что вам нужно создать инстантабельный подкласс вашего абстрактного класса.
Ответ 2
Если вам просто нужно проверить некоторые конкретные методы, не касаясь каких-либо рефератов, вы можете использовать CALLS_REAL_METHODS
(см. Мортен ответ), но если конкретный метод под тестированием вызывает некоторые тезисы или нереализованные интерфейсные методы, это не сработает - Mockito будет жаловаться "Невозможно вызвать реальный метод для интерфейса Java".
(Да, это паршивый дизайн, но некоторые рамки, например, Tapestry 4, навязывают вам силу.)
Обходной путь заключается в том, чтобы отменить этот подход - использовать обычное макетное поведение (т.е. все, что насмехалось/заштриховало), и использовать doCallRealMethod()
, чтобы явно вызвать конкретный метод, находящийся в процессе тестирования. Например.
public abstract class MyClass {
@SomeDependencyInjectionOrSomething
public abstract MyDependency getDependency();
public void myMethod() {
MyDependency dep = getDependency();
dep.doSomething();
}
}
public class MyClassTest {
@Test
public void myMethodDoesSomethingWithDependency() {
MyDependency theDependency = mock(MyDependency.class);
MyClass myInstance = mock(MyClass.class);
// can't do this with CALLS_REAL_METHODS
when(myInstance.getDependency()).thenReturn(theDependency);
doCallRealMethod().when(myInstance).myMethod();
myInstance.myMethod();
verify(theDependency, times(1)).doSomething();
}
}
Обновлено для добавления:
Для непустых методов вам нужно использовать thenCallRealMethod()
, например:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
В противном случае Mockito будет жаловаться "Обнаружено незавершенное обнаружение".
Ответ 3
Вы можете добиться этого, используя шпион (однако используйте последнюю версию Mockito 1.8+).
public abstract class MyAbstract {
public String concrete() {
return abstractMethod();
}
public abstract String abstractMethod();
}
public class MyAbstractImpl extends MyAbstract {
public String abstractMethod() {
return null;
}
}
// your test code below
MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));
Ответ 4
Mocking frameworks предназначены для облегчения издевательства зависимостей тестируемого класса. Когда вы используете фальшивую структуру для издевательства над классом, большинство фреймворков динамически создают подкласс и заменяют реализацию метода кодом для обнаружения, когда метод вызывается и возвращает поддельное значение.
При тестировании абстрактного класса вы хотите выполнить не абстрактные методы Subject Under Test (SUT), так что насмешливая структура не то, что вы хотите.
Часть путаницы заключается в том, что ответ на вопрос, который вы связали с упомянутым, применительно к ручному макету, который простирается от вашего абстрактного класса. Я бы не назвал такой класс фальшивым. Макет - это класс, который используется в качестве замены для зависимости, запрограммирован с ожиданиями и может быть запрошен, чтобы убедиться, что эти ожидания выполнены.
Вместо этого я предлагаю определить неабстрактный подкласс вашего абстрактного класса в вашем тесте. Если это приводит к слишком большому количеству кода, это может быть признаком того, что ваш класс трудно расширить.
Альтернативным решением было бы сделать ваш тестовый пример абстрактным, с абстрактным методом для создания SUT (другими словами, тестовый пример будет использовать Шаблон метода).
Ответ 5
Попробуйте использовать настраиваемый ответ.
Например:
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class CustomAnswer implements Answer<Object> {
public Object answer(InvocationOnMock invocation) throws Throwable {
Answer<Object> answer = null;
if (isAbstract(invocation.getMethod().getModifiers())) {
answer = Mockito.RETURNS_DEFAULTS;
} else {
answer = Mockito.CALLS_REAL_METHODS;
}
return answer.answer(invocation);
}
}
Он вернет макет для абстрактных методов и вызовет реальный метод для конкретных методов.
Ответ 6
Что действительно заставляет меня чувствовать себя плохо в насмешливых абстрактных классах, так это тот факт, что ни конструктор по умолчанию YourAbstractClass() не вызывается (отсутствует super() в mock), ни, похоже, не может быть каким-либо образом в Mockito по умолчанию инициализировать макетные свойства ( например, свойства списка с пустым ArrayList или LinkedList).
Мой абстрактный класс (в основном генерируется исходный код класса) НЕ предоставляет инъекцию установщика зависимостей для элементов списка, а также конструктор, в котором он инициализирует элементы списка (которые я пытался добавить вручную).
Только атрибуты класса используют инициализацию по умолчанию:
private Список dep1 = новый ArrayList;
private Список dep2 = новый ArrayList
Таким образом, нет никакого способа издеваться над абстрактным классом без использования реальной реализации объекта (например, определение внутреннего класса в классе unit test, переопределение абстрактных методов) и шпионаж реального объекта (который делает правильную инициализацию поля).
Слишком плохо, что только PowerMock поможет здесь далее.
Ответ 7
Предполагая, что ваши тестовые классы находятся в одном пакете (под другим исходным корнем) в качестве тестируемых классов, вы можете просто создать mock:
YourClass yourObject = mock(YourClass.class);
и вызовите методы, которые вы хотите протестировать, как и любой другой метод.
Вам нужно предоставить ожидания для каждого метода, который вызывается с ожиданием по любым конкретным методам, вызывающим супер метод, - не уверен, как вы это сделаете с Mockito, но я считаю, что это возможно с помощью EasyMock.
Все это делается для создания конкретного экземпляра YouClass
и сохранения усилий, связанных с предоставлением пустых реализаций каждого абстрактного метода.
В стороне я часто считаю полезным реализовать абстрактный класс в своем тесте, где он служит примером реализации, которую я тестирую через свой публичный интерфейс, хотя это зависит от функциональности, предоставляемой абстрактным классом.
Ответ 8
Вы можете расширить абстрактный класс с анонимного класса в своем тесте.
Например (с помощью Junit 4):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
Ответ 9
Вы можете создать экземпляр анонимного класса, ввести свои mocks и затем проверить этот класс.
@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {
private ClassUnderTest classUnderTest;
@Mock
MyDependencyService myDependencyService;
@Before
public void setUp() throws Exception {
this.classUnderTest = getInstance();
}
private ClassUnderTest getInstance() {
return new ClassUnderTest() {
private ClassUnderTest init(
MyDependencyService myDependencyService
) {
this.myDependencyService = myDependencyService;
return this;
}
@Override
protected void myMethodToTest() {
return super.myMethodToTest();
}
}.init(myDependencyService);
}
}
Имейте в виду, что видимость должна быть protected
для свойства myDependencyService
абстрактного класса ClassUnderTest
.
Ответ 10
Whitebox.invokeMethod(..) может пригодиться в этом случае.
Ответ 11
Mockito позволяет издеваться над абстрактными классами посредством аннотации @Mock
:
public abstract class My {
public abstract boolean myAbstractMethod();
public void myNonAbstractMethod() {
// ...
}
}
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
@Mock(answer = Answers.CALLS_REAL_METHODS)
private My my;
@Test
private void shouldPass() {
BDDMockito.given(my.myAbstractMethod()).willReturn(true);
my.myNonAbstractMethod();
// ...
}
}
Недостатком является то, что он не может использоваться, если вам нужны параметры конструктора.