Introduction to EasyMock – EasyMock简介

最后修改: 2018年 4月 7日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In the past, we’ve talked extensively about JMockit and Mockito.

在过去,我们已经广泛地讨论了JMockitMockito

In this tutorial, we’ll give an introduction to another mocking tool – EasyMock.

在本教程中,我们将介绍另一个嘲讽工具 – EasyMock

2. Maven Dependencies

2.Maven的依赖性

Before we dive in, let’s add the following dependency to our pom.xml:

在我们深入研究之前,让我们在pom.xml中添加以下依赖关系。

<dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <version>3.5.1</version>
    <scope>test</scope>
</dependency>

The latest version can always be found here.

最新版本总是可以在这里找到。

3. Core Concepts

3.核心概念

When generating a mock, we can simulate the target object, specify its behavior, and finally verify whether it’s used as expected.

在生成模拟时,我们可以模拟目标对象,指定其行为,并最终验证其是否按照预期使用。

Working with EasyMock’s mocks involves four steps:

使用EasyMock的模拟工作包括四个步骤。

  1. creating a mock of the target class
  2. recording its expected behavior, including the action, result, exceptions, etc.
  3. using mocks in tests
  4. verifying if it’s behaving as expected

After our recording finishes, we switch it to “replay” mode, so that the mock behaves as recorded when collaborating with any object that will be using it.

录制完成后,我们把它切换到 “重放 “模式,这样,在与任何将使用它的对象协作时,模拟的行为就像录制的一样。

Eventually, we verify if everything goes as expected.

最终,我们验证是否一切如预期般顺利。

The four steps mentioned above relate to methods in org.easymock.EasyMock:

上面提到的四个步骤与org.easymock.EasyMock中的方法有关。

  1. 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
  2. expect(…): with this method, we can set expectations, including calls, results, and exceptions, for associated recording actions
  3. replay(…): switches a given mock to “replay” mode. Then, any action triggering previously recorded method calls will replay “recorded results”
  4. verify(…): verifies that all expectations were met and that no unexpected call was performed on a mock

In the next section, we’ll show how these steps work in action, using real-world examples.

在下一节中,我们将使用真实世界的例子,展示这些步骤是如何在行动中发挥作用的。

4. A Practical Example of Mocking

4.嘲弄的实际例子

Before we continue, let’s take a look at the example context: say we have a reader of the Baeldung blog, who likes to browse articles on the website, and then he/she tries to write articles.

在我们继续之前,让我们看一下例子的背景:假设我们有一个Baeldung博客的读者,他喜欢浏览网站上的文章,然后他/她尝试着写文章。

Let’s start by creating the following model:

让我们从创建以下模型开始。

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

In this model, we have two private members: the articleReader(a concrete class) and the articleWriter (an interface).

在这个模型中,我们有两个私有成员:articleReader(一个具体类)和articleWriter(一个接口)。

Next, we’ll mock them to verify BaeldungReader‘s behavior.

接下来,我们将模拟它们来验证BaeldungReader的行为。

5. Mock With Java Code

5.用Java代码进行模拟

Let’s begin with mocking an ArticleReader.

让我们从模拟一个ArticleReader开始。

5.1. Typical Mocking

5.1.典型的嘲弄

We expect the articleReader.next() method to be called when a reader skips an article:

我们希望当读者跳过一篇文章时,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);
}

In the sample code above, we stick strictly to the 4-step procedure and mock the ArticleReader class.

在上面的示例代码中,我们严格遵守4步程序,模拟ArticleReader类。

Although we really don’t care what mockArticleReader.next() returns, we still need to specify a return value for mockArticleReader.next() by using expect(…).andReturn(…).

尽管我们真的不关心mockArticleReader.next()的返回值,我们仍然需要通过使用expect(…).andReturn(…)来为mockArticleReader.next()指定一个返回值

With expect(…), EasyMock is expecting the method to return a value or throw an Exception.

使用expect(…),EasyMock期望该方法返回一个值或抛出一个Exception.

If we simply do:

如果我们简单地做。

mockArticleReader.next();
replay(mockArticleReader);

EasyMock will complain about this, as it requires a call on expect(…).andReturn(…) if the method returns anything.

EasyMock会抱怨这一点,因为它需要调用expect(…).andReturn(…),如果方法返回任何东西的话。

If it’s a void method, we can expect its action using expectLastCall() like this:

如果它是一个void方法,我们可以使用expectLastCall()这样的方式来期待其动作。

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. Replay Order

5.2.回放顺序

If we need actions to be replayed in a specific order, we can be more strict:

如果我们需要动作以特定的顺序重放,我们可以更加严格。

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

In this snippet, we use strictMock(…) to check the order of method calls. For mocks created by mock(…) and strictMock(…), any unexpected method calls would cause an AssertionError.

在这个片段中,我们使用strictMock(…)来检查方法调用的顺序。对于由mock(…)strictMock(…)创建的mock,任何意外的方法调用都会导致AssertionError

To allow any method call for the mock, we can use niceMock(…):

为了允许模拟的任何方法调用,我们可以使用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.

在这里我们并不期望baeldungReader.readTopic(…)被调用,但EasyMock不会抱怨。有了niceMock(…),EasyMock现在只关心目标对象是否执行了预期动作。

5.3. Mocking Exception Throws

5.3.嘲弄Exception抛出

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

现在,让我们继续模拟接口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());
}

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

在上面的片段中,我们期望articleWriter足够坚实,以检测XSS(跨站脚本)攻击。

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(…).

因此,当读者试图在文章内容中注入恶意代码时,作者应该抛出一个IllegalArgumentException。我们使用expect(…).andThrow(…)来记录这一预期行为。

6. Mock With Annotation

6.带注释的模拟

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.

EasyMock还支持使用注解来注入模拟。为了使用它们,我们需要用EasyMockRunner运行我们的单元测试,以便它处理@Mock@TestSubject注解。

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.

相当于mock(…),一个mock将被注入到用@Mock注释的字段中。而这些模拟将被注入到用@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.

在上面的片段中,我们没有显式地初始化articleReader字段在baeldungReader.调用baeldungReader.readNext()时,我们可以inter那个隐式调用mockArticleReader

That was because mockArticleReader was injected to the articleReader field.

这是因为mockArticleReader被注入到articleReader字段。

Note that if we want to use another test runner instead of EasyMockRunner, we can use the JUnit test rule EasyMockRule:

注意,如果我们想使用另一个测试运行器而不是EasyMockRunner,我们可以使用JUnit测试规则EasyMockRulea>:

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

7.用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:

幸运的是,我们在EasyMock中拥有一个EasyMockSupport类来帮助处理这个问题。它帮助跟踪模拟,这样我们就可以像这样批量地重放和验证它们。

//...
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.

这里我们模拟了articleReaderarticleWriter。当把这些模拟设置为 “重放 “模式时,我们使用了EasyMockSupport提供的静态方法replayAll(),并使用verifyAll()来验证它们在批次中的行为。

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.

我们还在expect阶段引入了times(…)方法。它有助于指定我们期望该方法被调用多少次,这样我们就可以避免引入重复的代码。

We can also use EasyMockSupport through delegation:

我们也可以通过委托使用EasyMockSupport

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.

以前,我们使用静态方法或注解来创建和管理模拟。在引擎盖下,这些静态和注解的模拟由一个全局EasyMockSupport实例控制。

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.

在这里,我们明确地将其实例化,并通过授权将所有这些模拟置于我们自己的控制之下。如果我们的测试代码与EasyMock有任何名称冲突或有任何类似的情况,这可能有助于避免混淆。

8. Conclusion

8.结论

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.

在这篇文章中,我们简单介绍了EasyMock的基本用法,关于如何生成模拟对象,记录和重放它们的行为,并验证它们的行为是否正确。

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

如果您可能感兴趣,请查看这篇文章,以了解 EasyMock、Mocket 和 JMockit 的比较。

As always, the full implementation can be found over on Github.

一如既往,完整的实现可以在Github上找到