«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 включает четыре шага:
- creating a mock of the target class
- recording its expected behavior, including the action, result, exceptions, etc.
- using mocks in tests
- verifying if it’s behaving as expected
После завершения записи мы переключаем ее в режим «воспроизведения», чтобы макет вел себя так, как записан, при сотрудничестве с любым объектом, который будет его использовать.
В конце концов, мы проверяем, все ли идет так, как ожидалось.
Четыре шага, упомянутые выше, относятся к методам в org.easymock.EasyMock:
- mock(…): generates a mock of the target class, be it a concrete class or an interface. Once created, a mock is in “recording” mode, meaning that EasyMock will record any action the Mock Object takes, and replay them in the “replay” mode
- expect(…): with this method, we can set expectations, including calls, results, and exceptions, for associated recording actions
- replay(…): switches a given mock to “replay” mode. Then, any action triggering previously recorded method calls will replay “recorded results”
- verify(…): verifies that all expectations were met and that no unexpected call was performed on a mock
В следующем разделе мы покажем, как эти шаги работают в действии, используя примеры из реальной жизни.
4. Практический пример мока
Прежде чем мы продолжим, давайте взглянем на контекст примера: скажем, у нас есть читатель блога Baeldung, который любит просматривать статьи на веб-сайте, а затем он/она пытается писать статьи.
Начнем с создания следующей модели:
public class BaeldungReader {
private ArticleReader articleReader;
private IArticleWriter articleWriter;
// constructors
public BaeldungArticle readNext(){
return articleReader.next();
}
public List<BaeldungArticle> readTopic(String topic){
return articleReader.ofTopic(topic);
}
public String write(String title, String content){
return articleWriter.write(title, content);
}
}
В этой модели у нас есть два закрытых члена: articleReader (конкретный класс) и articleWriter (интерфейс).
Далее мы создадим их моки, чтобы проверить поведение BaeldungReader.
5. Мок с Java-кодом
Давайте начнем с создания макета для ArticleReader.
5.1. Типичная насмешка
Мы ожидаем, что метод articleReader.next() будет вызываться, когда читатель пропускает статью:
@Test
public void whenReadNext_thenNextArticleRead(){
ArticleReader mockArticleReader = mock(ArticleReader.class);
BaeldungReader baeldungReader
= new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.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);
BaeldungReade baeldungReader
= new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
baeldungReader.readTopic("easymock");
verify(mockArticleReader);
}
В этом фрагменте мы используем strictMock(…) для проверки порядка вызовов методов. Для макетов, созданных mock(…) и strictMock(…), любые неожиданные вызовы методов вызовут AssertionError.
Чтобы разрешить любой вызов метода для макета, мы можем использовать niceMock(…):
@Test
public void whenReadNextAndOthers_thenAllowed(){
ArticleReader mockArticleReader = niceMock(ArticleReader.class);
BaeldungReade baeldungReader = new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
baeldungReader.readTopic("easymock");
verify(mockArticleReader);
}
Здесь мы не ожидали вызова baeldungReader.readTopic(…), но EasyMock победил Не жалуйся. С niceMock(…) EasyMock теперь заботится только о том, выполнил ли целевой объект ожидаемое действие или нет.
5.3. Имитация Exception Throws
Теперь давайте продолжим имитацию интерфейса IArticleWriter и то, как обрабатывать ожидаемые Throwables:
@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
// mocking and initialization
expect(mockArticleWriter
.write("easymock","<body onload=alert('baeldung')>"))
.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 BaeldungReaderAnnotatedTest {
@Mock
ArticleReader mockArticleReader;
@TestSubject
BaeldungReader baeldungReader = new BaeldungReader();
@Test
public void whenReadNext_thenNextArticleRead() {
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
verify(mockArticleReader);
}
}
Эквивалент mock(…), макет будет вставлен в поля, аннотированные с помощью @Mock. И эти макеты будут внедрены в поля класса, аннотированные @TestSubject.
В приведенном выше фрагменте мы явно не инициализировали поле articleReader в baeldungReader. При вызове baeldungReader.readNext() мы можем интерпретировать неявно вызываемый mockArticleReader.
Это произошло потому, что mockArticleReader был внедрен в поле articleReader.
«Обратите внимание, что если мы хотим использовать другое средство запуска тестов вместо EasyMockRunner, мы можем использовать тестовое правило JUnit EasyMockRule: нам приходится повторять вручную:
public class BaeldungReaderAnnotatedWithRuleTest {
@Rule
public EasyMockRule mockRule = new EasyMockRule(this);
//...
@Test
public void whenReadNext_thenNextArticleRead(){
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
verify(mockArticleReader);
}
}
Это некрасиво, и нам нужно элегантное решение.
К счастью, у нас есть класс EasyMockSupport в EasyMock, который поможет справиться с этим. Это помогает отслеживать моки, чтобы мы могли воспроизвести и проверить их в пакете следующим образом:
replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);
Здесь мы имитировали как articleReader, так и articleWriter. При установке этих макетов в режим «воспроизведения» мы использовали статический метод replayAll(), предоставленный EasyMockSupport, и использовали verifyAll() для проверки их поведения в пакетном режиме.
Мы также ввели метод times(…) на этапе ожидания. Это помогает указать, сколько раз мы ожидаем, что метод будет вызываться, чтобы мы могли избежать дублирования кода.
//...
public class BaeldungReaderMockSupportTest 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);
}
}
Мы также можем использовать EasyMockSupport через делегирование:
Раньше мы использовали статические методы или аннотации для создания макетов и управления ими. Под капотом эти статические и аннотированные макеты контролируются глобальным экземпляром EasyMockSupport.
Здесь мы явно создали его экземпляр и взяли все эти моки под свой собственный контроль через делегирование. Это может помочь избежать путаницы, если в нашем тестовом коде с EasyMock возникнут конфликты имен или возникнут какие-либо подобные случаи.
EasyMockSupport easyMockSupport = new EasyMockSupport();
@Test
public void whenReadAndWriteSequencially_thenWorks(){
ArticleReader mockArticleReader = easyMockSupport
.createMock(ArticleReader.class);
IArticleWriter mockArticleWriter = easyMockSupport
.createMock(IArticleWriter.class);
BaeldungReader baeldungReader = new BaeldungReader(
mockArticleReader, mockArticleWriter);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleWriter.write("title", "content"))
.andReturn("");
easyMockSupport.replayAll();
baeldungReader.readNext();
baeldungReader.write("title", "content");
easyMockSupport.verifyAll();
}
8. Заключение
В этой статье мы кратко рассказали об основах использования EasyMock, о том, как генерировать фиктивные объекты, записывать и воспроизводить их поведение, а также проверять правильность их поведения.
Если вам интересно, ознакомьтесь с этой статьей для сравнения EasyMock, Mocket и JMockit.
Как всегда, полную реализацию можно найти на Github.
«
As always, the full implementation can be found over on Github.