1. Введение
В прошлом мы много говорили о JMockit и Mockito .
В этом уроке мы познакомим вас с другим инструментом для мокаинга — EasyMock .
2. Зависимости Maven
Прежде чем мы углубимся, давайте добавим следующую зависимость в наш pom.xml
:
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.5.1</version>
<scope>test</scope>
</dependency>
Последнюю версию всегда можно найти здесь .
3. Основные концепции
При создании макета мы можем имитировать целевой объект, указать его поведение и, наконец, проверить, используется ли он должным образом.
Работа с макетами EasyMock включает четыре шага:
- создание макета целевого класса
- запись его ожидаемого поведения, включая действие, результат, исключения и т. д.
- использование моков в тестах
- проверка, ведет ли он себя так, как ожидалось
После завершения записи мы переключаем ее в режим «воспроизведения», чтобы макет вел себя так, как записан, при сотрудничестве с любым объектом, который будет его использовать.
В конце концов, мы проверяем, все ли идет так, как ожидалось.
Четыре упомянутых выше шага относятся к методам в org.easymock.EasyMock
:
mock(…)
: создает макет целевого класса, будь то конкретный класс или интерфейс. После создания макет находится в режиме «записи», что означает, что EasyMock запишет любое действие, которое выполняет макет объекта, и воспроизведет их в режиме «воспроизведения».expect(…)
: с помощью этого метода мы можем установить ожидания, включая вызовы, результаты и исключения, для связанных действий записи.replay(…)
: переключает данный макет в режим «воспроизведения». Затем любое действие, запускающее ранее записанные вызовы методов, будет воспроизводить «записанные результаты».Verify(…)
: проверяет, что все ожидания оправдались и не было выполнено никаких неожиданных вызовов на макете. ****
В следующем разделе мы покажем, как эти шаги работают в действии, на реальных примерах.
4. Практический пример мокинга
Прежде чем мы продолжим, давайте взглянем на пример контекста: скажем, у нас есть читатель блога ForEach, которому нравится просматривать статьи на веб-сайте, а затем он/она пытается писать статьи.
Начнем с создания следующей модели:
public class ForEachReader {
private ArticleReader articleReader;
private IArticleWriter articleWriter;
// constructors
public ForEachArticle readNext(){
return articleReader.next();
}
public List<ForEachArticle> readTopic(String topic){
return articleReader.ofTopic(topic);
}
public String write(String title, String content){
return articleWriter.write(title, content);
}
}
В этой модели у нас есть два закрытых члена: articleReader
(конкретный класс) и articleWriter
(интерфейс).
Затем мы создадим им макет, чтобы проверить поведение ForEachReader
.
5. Мок с Java-кодом
Давайте начнем с создания объекта ArticleReader
.
5.1. Типичное издевательство
Мы ожидаем, что метод articleReader.next()
будет вызываться, когда читатель пропускает статью:
@Test
public void whenReadNext_thenNextArticleRead(){
ArticleReader mockArticleReader = mock(ArticleReader.class);
ForEachReader foreachReader
= new ForEachReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
verify(mockArticleReader);
}
В приведенном выше примере кода мы строго придерживаемся четырехэтапной процедуры и моделируем класс ArticleReader .
Хотя нас действительно не волнует, что возвращает mockArticleReader.next()
, нам все же нужно указать возвращаемое значение для mockArticleReader.next()
с помощью expect(…).andReturn(…).
С expect(…)
EasyMock ожидает, что метод вернет значение или выдаст исключение.
Если мы просто сделаем:
mockArticleReader.next();
replay(mockArticleReader);
EasyMock будет жаловаться на это, так как требует вызова expect(…).andReturn(…)
если метод что-то возвращает.
Если это метод void
, мы можем ожидать
его действия с помощью expectLastCall()
следующим образом:
mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);
5.2. Порядок воспроизведения
Если нам нужно, чтобы действия воспроизводились в определенном порядке, мы можем быть более строгими:
@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
ArticleReader mockArticleReader
= strictMock(ArticleReader.class);
ForEachReade foreachReader
= new ForEachReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
foreachReader.readTopic("easymock");
verify(mockArticleReader);
}
В этом фрагменте мы используем strictMock(…)
для проверки порядка вызовов методов . Для макетов, созданных mock(…)
и strictMock(…)
, любые неожиданные вызовы методов вызовут AssertionError
.
Чтобы разрешить любой вызов метода для макета, мы можем использовать niceMock(…)
:
@Test
public void whenReadNextAndOthers_thenAllowed(){
ArticleReader mockArticleReader = niceMock(ArticleReader.class);
ForEachReade foreachReader = new ForEachReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
foreachReader.readTopic("easymock");
verify(mockArticleReader);
}
Здесь мы не ожидали вызова foreachReader.readTopic(…)
, но EasyMock не будет жаловаться. С niceMock(…)
EasyMock теперь заботится только о том, выполнил ли целевой объект ожидаемое действие или нет.
5.3. Имитация исключений
_
Теперь давайте продолжим издеваться над интерфейсом IArticleWriter
и как обрабатывать ожидаемые Throwables
:
@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
// mocking and initialization
expect(mockArticleWriter
.write("easymock","<body onload=alert('foreach')>"))
.andThrow(new IllegalArgumentException());
replay(mockArticleWriter);
// write malicious content and capture exception as expectedException
verify(mockArticleWriter);
assertEquals(
IllegalArgumentException.class,
expectedException.getClass());
}
В приведенном выше фрагменте мы ожидаем, что articleWriter
достаточно надежен, чтобы обнаруживать атаки XSS (межсайтовый скриптинг) .
Поэтому, когда читатель пытается внедрить вредоносный код в содержимое статьи, автор должен выдать исключение IllegalArgumentException
. Мы записали это ожидаемое поведение, используя expect(…).andThrow(…)
.
6. Макет с аннотацией
EasyMock также поддерживает внедрение макетов с помощью аннотаций. Чтобы использовать их, нам нужно запустить наши модульные тесты с помощью EasyMockRunner
, чтобы он обрабатывал аннотации @Mock
и @TestSubject
.
Перепишем предыдущие фрагменты:
@RunWith(EasyMockRunner.class)
public class ForEachReaderAnnotatedTest {
@Mock
ArticleReader mockArticleReader;
@TestSubject
ForEachReader foreachReader = new ForEachReader();
@Test
public void whenReadNext_thenNextArticleRead() {
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
verify(mockArticleReader);
}
}
Эквивалент mock(…)
, макет будет вставлен в поля, аннотированные с помощью @Mock
. И эти моки будут внедряться в поля класса, аннотированные @TestSubject
.
В приведенном выше фрагменте мы явно не инициализировали поле articleReader
в foreachReader.
При вызове foreachReader.readNext()
мы можем интерпретировать неявно вызываемый mockArticleReader
.
Это произошло потому , что mockArticleReader
был внедрен в поле articleReader
.
Обратите внимание, что если мы хотим использовать другое средство запуска тестов вместо EasyMockRunner
, мы можем использовать тестовое правило JUnit EasyMockRule
:
public class ForEachReaderAnnotatedWithRuleTest {
@Rule
public EasyMockRule mockRule = new EasyMockRule(this);
//...
@Test
public void whenReadNext_thenNextArticleRead(){
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
foreachReader.readNext();
verify(mockArticleReader);
}
}
7. Мок с EasyMockSupport
Иногда нам нужно ввести несколько моков в одном тесте, и нам приходится повторять вручную:
replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);
Это некрасиво, и нам нужно элегантное решение.
К счастью, у нас есть класс EasyMockSupport
в EasyMock , который поможет справиться с этим. Это помогает отслеживать макеты, чтобы мы могли воспроизвести и проверить их в пакете, подобном этому:
//...
public class ForEachReaderMockSupportTest extends EasyMockSupport{
//...
@Test
public void whenReadAndWriteSequencially_thenWorks(){
expect(mockArticleReader.next()).andReturn(null)
.times(2).andThrow(new NoSuchElementException());
expect(mockArticleWriter.write("title", "content"))
.andReturn("BAEL-201801");
replayAll();
// execute read and write operations consecutively
verifyAll();
assertEquals(
NoSuchElementException.class,
expectedException.getClass());
assertEquals("BAEL-201801", articleId);
}
}
Здесь мы имитировали как articleReader
, так и articleWriter
. При установке этих макетов в режим «воспроизведения» мы использовали статический метод replayAll()
, предоставляемый EasyMockSupport
, и использовали verifyAll()
для проверки их поведения в пакетном режиме.
Мы также ввели метод times(…)
на этапе ожидания
. Это помогает указать, сколько раз мы ожидаем, что метод будет вызываться, чтобы мы могли избежать дублирования кода.
Мы также можем использовать EasyMockSupport
через делегирование:
EasyMockSupport easyMockSupport = new EasyMockSupport();
@Test
public void whenReadAndWriteSequencially_thenWorks(){
ArticleReader mockArticleReader = easyMockSupport
.createMock(ArticleReader.class);
IArticleWriter mockArticleWriter = easyMockSupport
.createMock(IArticleWriter.class);
ForEachReader foreachReader = new ForEachReader(
mockArticleReader, mockArticleWriter);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleWriter.write("title", "content"))
.andReturn("");
easyMockSupport.replayAll();
foreachReader.readNext();
foreachReader.write("title", "content");
easyMockSupport.verifyAll();
}
Раньше мы использовали статические методы или аннотации для создания макетов и управления ими. Под капотом эти статические и аннотированные макеты контролируются глобальным экземпляром EasyMockSupport
.
Здесь мы явно создали его экземпляр и взяли все эти моки под свой собственный контроль через делегирование. Это может помочь избежать путаницы, если в нашем тестовом коде с EasyMock возникнут конфликты имен или возникнут какие-либо подобные случаи.
8. Заключение
В этой статье мы кратко представили основы использования EasyMock, о том, как создавать фиктивные объекты, записывать и воспроизводить их поведение, а также проверять правильность их поведения.
Если вам интересно, ознакомьтесь с этой статьей для сравнения EasyMock, Mocket и JMockit.
Как всегда, полную реализацию можно найти на Github .