Въведение в EasyMock

1. Въведение

В миналото говорихме подробно за JMockit и Mockito.

В този урок ще дадем въведение на друг подигравателен инструмент - EasyMock.

2. Зависимости на Maven

Преди да се потопим, нека добавим следната зависимост към нашия pom.xml :

 org.easymock easymock 3.5.1 test 

Най-новата версия винаги може да бъде намерена тук.

3. Основни концепции

Когато генерираме макет, можем да симулираме целевия обект, да посочим поведението му и накрая да проверим дали се използва според очакванията.

Работата с макетите на EasyMock включва четири стъпки:

  1. създаване на макет на целевия клас
  2. записване на очакваното му поведение, включително действие, резултат, изключения и др.
  3. използване на подигравки в тестове
  4. проверка дали се държи според очакванията

След като записът ни завърши, ние го превключваме в режим „повторно възпроизвеждане“, така че макетът да се държи така, както е записан, когато си сътрудничим с всеки обект, който ще го използва.

В крайна сметка проверяваме дали всичко върви според очакванията.

Четирите стъпки, споменати по-горе, се отнасят до методи в org.easymock.EasyMock :

  1. mock (...) : генерира макет на целевия клас, било то конкретен клас или интерфейс. Веднъж създаден, макетът е в режим „запис”, което означава, че EasyMock ще записва всяко действие, което Mock Object предприеме, и ще ги възпроизвежда в режим „replay”
  2. очаквайте (...) : с този метод можем да зададем очаквания, включително обаждания, резултати и изключения, за свързани действия по записване
  3. replay (…) : превключва даден макет в режим „replay“. След това всяко действие, задействащо предварително записани извиквания на методи, ще повтори „записани резултати“
  4. verify (…) : проверява дали всички очаквания са били изпълнени и че не е извършено неочаквано обаждане в макет

В следващия раздел ще покажем как тези стъпки работят в действие, като използваме реални примери.

4. Практически пример за подигравка

Преди да продължим, нека да разгледаме примерния контекст: да кажем, че имаме читател на блога Baeldung, който обича да разглежда статии на уебсайта и след това се опитва да пише статии.

Нека започнем със създаването на следния модел:

public class BaeldungReader { private ArticleReader articleReader; private IArticleWriter articleWriter; // constructors public BaeldungArticle readNext(){ return articleReader.next(); } public List 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); }

В примерния код по-горе се придържаме стриктно към процедурата от 4 стъпки и се подиграваме на класа ArticleReader .

Въпреки че наистина не ни интересува какво връща mockArticleReader.next () , все пак трябва да посочим възвръщаема стойност за mockArticleReader.next (), като използваме awa (...) .andReturn (...).

С очаквайте (...) , EasyMock очаква методът да върне стойност или да изведе изключение.

Ако просто направим:

mockArticleReader.next(); replay(mockArticleReader);

EasyMock ще се оплаче от това, тъй като изисква обаждане на очакването (...). AndReturn (...), ако методът върне нещо.

Ако това е метод void , можем да очакваме действието му с помощта на awaLastCall () по следния начин:

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); }

Here we didn't expect the baeldungReader.readTopic(…) to be called, but EasyMock won't complain. With niceMock(…), EasyMock now only cares if the target object performed expected action or not.

5.3. Mocking Exception Throws

Now, let's continue with mocking the interface IArticleWriter, and how to handle expected Throwables:

@Test public void whenWriteMaliciousContent_thenArgumentIllegal() { // mocking and initialization expect(mockArticleWriter .write("easymock","")) .andThrow(new IllegalArgumentException()); replay(mockArticleWriter); // write malicious content and capture exception as expectedException verify(mockArticleWriter); assertEquals( IllegalArgumentException.class, expectedException.getClass()); }

In the snippet above, we expect the articleWriter is solid enough to detect XSS(Cross-site Scripting) attacks.

So when the reader tries to inject malicious code into the article content, the writer should throw an IllegalArgumentException. We recorded this expected behavior using expect(…).andThrow(…).

6. Mock With Annotation

EasyMock also supports injecting mocks using annotations. To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.

Let's rewrite previous snippets:

@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); } }

Equivalent to mock(…), a mock will be injected into fields annotated with @Mock. And these mocks will be injected into fields of the class annotated with @TestSubject.

In the snippet above, we didn't explicitly initialize the articleReader field in baeldungReader. When calling baeldungReader.readNext(), we can inter that implicitly called mockArticleReader.

That was because mockArticleReader was injected to the articleReader field.

Note that if we want to use another test runner instead of EasyMockRunner, we can use the JUnit test rule 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); } }

7. Mock With EasyMockSupport

Sometimes we need to introduce multiple mocks in a single test, and we have to repeat manually:

replay(A); replay(B); replay(C); //... verify(A); verify(B); verify(C);

This is ugly, and we need an elegant solution.

Luckily, we have a class EasyMockSupport in EasyMock to help deal with this. It helps keep track of mocks, such that we can replay and verify them in a batch like this:

//... 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); } }

Here we mocked both articleReader and articleWriter. When setting these mocks to “replay” mode, we used a static method replayAll() provided by EasyMockSupport, and used verifyAll() to verify their behaviors in batch.

We also introduced times(…) method in the expect phase. It helps specify how many times we expect the method to be called so that we can avoid introducing duplicate code.

We can also use EasyMockSupport through delegation:

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(); }

Previously, we used static methods or annotations to create and manage mocks. Under the hood, these static and annotated mocks are controlled by a global EasyMockSupport instance.

Here, we explicitly instantiated it and take all these mocks under our own control, through delegation. This may help avoid confusion if there's any name conflicts in our test code with EasyMock or be there any similar cases.

8. Conclusion

In this article, we briefly introduced the basic usage of EasyMock, about how to generate mock objects, record and replay their behaviors, and verify if they behaved correctly.

In case you may be interested, check out this article for a comparison of EasyMock, Mocket, and JMockit.

Както винаги, пълното внедряване може да бъде намерено в Github.