JMockit Advanced Usage – JMockit的高级用法

最后修改: 2016年 8月 2日

1. Introduction

1.介绍

In this article, we’ll go beyond the JMockit basics and we’ll start looking at some advanced scenarios, such as:

在这篇文章中,我们将超越JMockit的基础知识,开始关注一些高级场景,例如。

  • Faking (or the MockUp API)
  • The Deencapsulation utility class
  • How to mock more than one interface using only one mock
  • How to reuse expectations and verifications

If you want to discover JMockit’s basics, check other articles from this series. You can find relevant links at the bottom of the page.

如果你想了解JMockit的基础知识,请查看本系列的其他文章。你可以在页面底部找到相关链接。

2. Maven Dependency

2.Maven的依赖性

First, we’ll need to add the jmockit dependency to our project:

首先,我们需要将jmockit依赖性添加到我们的项目中。

<dependency> 
    <groupId>org.jmockit</groupId> 
    <artifactId>jmockit</artifactId> 
    <version>1.41</version>
</dependency>

Next, we’ll continue with the examples.

接下来,我们将继续举例说明。

3. Private Methods/Inner Classes Mocking

3.私有方法/内部类的嘲弄

Mocking and testing of private methods or inner classes is often not considered good practice.

对私有方法或内部类的嘲弄和测试通常不被认为是好的做法。

The reasoning behind it is that if they’re private, they shouldn’t be tested directly as they’re the innermost guts of the class, but sometimes it still needs to be done, especially when dealing with legacy code.

背后的理由是,如果它们是私有的,就不应该直接测试,因为它们是类的最内层,但有时仍然需要这样做,特别是在处理遗留代码时。

With JMockit, you have two options to handle these:

使用JMockit,你有两个选择来处理这些问题。

  • The MockUp API to alter the real implementation (for the second case)
  • The Deencapsulation utility class, to call any method directly (for the first case)

All following examples will be done for the following class and we’ll suppose that are run on a test class with the same configuration as the first one (to avoid repeating code):

下面所有的例子都是为下面的类做的,我们将假设这些例子是在一个测试类上运行的,其配置与第一个类相同(以避免重复代码)。

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

    // default constructor omitted 
    
    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}

3.1. Faking With MockUp

3.1.用MockUp作假

JMockit’s Mockup API provides support for the creation of fake implementations or mock-ups. Typically, a mock-up targets a few methods and/or constructors in the class to be faked, while leaving most other methods and constructors unmodified. This allows for a complete re-write of a class, so any method or constructor (with any access modifier) can be targeted.

JMockit的Mockup API提供了对创建假实现或mock-ups的支持。通常情况下,mock-up针对类中的几个方法和/或构造函数进行伪造,而其他大多数方法和构造函数则不作修改。这允许完全重写一个类,所以任何方法或构造函数(具有任何访问修改器)都可以成为目标。

Let’s see how we can re-define privateMethod() using the Mockup’s API:

让我们看看我们如何使用Mockup的API重新定义privateMethod()

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp<AdvancedCollaborator>() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

In this example we’re defining a new MockUp for the AdvancedCollaborator class using the @Mock annotation on a method with matching signature. After this, calls to that method will be delegated to our mocked one.

在这个例子中,我们为AdvancedCollaborator类定义了一个新的MockUp,使用@Mock注解在一个具有匹配签名的方法上。此后,对该方法的调用将被委托给我们的模拟方法。

We can also use this to mock-up the constructor of a class that needs specific arguments or configuration in order to simplify tests:

我们也可以用它来模拟一个需要特定参数或配置的类的构造函数,以简化测试。

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp<AdvancedCollaborator>() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

In this example, we can see that for constructor mocking you need to mock the $init method. You can pass an extra argument of type Invocation, with which you can access information about the invocation of the mocked method, including the instance to which the invocation is being performed.

在这个例子中,我们可以看到,对于构造函数的嘲弄,你需要嘲弄$init方法。你可以传递一个额外的Invocation,类型的参数,通过它你可以访问关于被模拟方法的调用的信息,包括正在执行的调用的实例。

3.2. Using the Deencapsulation Class

3.2.使用Deencapsulation

JMockit includes a test utility class: the Deencapsulation. As its name indicates, it’s used to de-encapsulate a state of an object, and using it, you can simplify testing by accessing fields and methods that could not be accessed otherwise.

JMockit包括一个测试工具类:Deencapsulation。正如它的名字所示,它被用来解封一个对象的状态,使用它,你可以通过访问否则无法访问的字段和方法来简化测试。

You can invoke a method:

你可以调用一个方法。

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

You can also set fields:

你也可以设置字段。

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

And get fields:

并获得领域。

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

And create new instances of classes:

并创建新的类的实例。

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

Even new instances of inner classes:

甚至是内部类的新实例。

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

As you can see, the Deencapsulation class is extremely useful when testing air tight classes. One example could be to set dependencies of a class that uses @Autowired annotations on private fields and has no setters for them, or to unit test inner classes without having to depend on the public interface of its container class.

正如你所看到的,Deencapsulation类在测试气密类时非常有用。一个例子是设置一个在私有字段上使用@Autowired注解且没有设置器的类的依赖性,或者对内层类进行单元测试而不必依赖其容器类的公共接口。

4. Mocking Multiple Interfaces in One Same Mock

4.在同一个Mock中模拟多个接口

Let’s assume that you want to test a class – not yet implemented – but you know for sure that it will implement several interfaces.

让我们假设你想测试一个类–尚未实现–但你肯定知道它将实现几个接口。

Usually, you wouldn’t be able to test said class before implementing it, but with JMockit you have the ability to prepare tests beforehand by mocking more than one interface using one mock object.

通常情况下,你无法在实现该类之前对其进行测试,但有了JMockit,你可以通过使用一个模拟对象对多个接口进行模拟,从而提前准备测试。

This can be achieved by using generics and defining a type that extends several interfaces. This generic type can be either defined for a whole test class or for just one test method.

这可以通过使用泛型和定义一个扩展了几个接口的类型来实现。这个泛型可以为整个测试类定义,也可以只为一个测试方法定义。

For example, we’re going to create a mock for interfaces List and Comparable two ways:

例如,我们将为接口List Comparable 的两种方式创建一个模拟

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest<MultiMock
  extends List<String> & Comparable<List<String>>> {
    
    @Mocked
    private MultiMock multiMock;
    
    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public <M extends List<String> & Comparable<List<String>>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List<String>) any); result = 0; 
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

As you can see in line 2, we can define a new test type for the whole test by using generics on the class name. That way, MultiMock will be available as a type and you’ll be able to create mocks for it using any of JMockit’s annotations.

正如你在第2行看到的,我们可以通过在类名上使用泛型来为整个测试定义一个新的测试类型。这样,MultiMock将作为一个类型可用,你将能够使用JMockit的任何注释为它创建模拟。

In lines from 7 to 18, we can see an example using a mock of a multi-class defined for the whole test class.

在第7行到第18行,我们可以看到一个使用多类的模拟来定义整个测试类的例子。

If you need the multi-interface mock for just one test, you can achieve this by defining the generic type on the method signature and passing a new mock of that new generic as the test method argument. In lines 20 to 32, we can see an example of doing so for the same tested behavior as in the previous test.

如果你只需要一个测试的多接口模拟,你可以通过在方法签名上定义泛型并传递一个新的泛型的模拟作为测试方法参数来实现。在第20行到第32行,我们可以看到一个这样做的例子,与前面的测试中的测试行为相同。

5. Reusing Expectations and Verifications

5.重用期望值和验证

In the end, when testing classes, you may encounter cases where you’re repeating the same Expectations and/or Verifications over and over. To ease that, you can reuse both easily.

最后,在测试类时,你可能会遇到重复相同Expectations和/或Verifications的情况。为了缓解这种情况,你可以轻松地重用这两者。

We’re going to explain it by an example (we’re using the classes Model, Collaborator, and Performer from our JMockit 101 article):

我们将通过一个例子来解释(我们使用JMockit 101文章中的Model, Collaborator, 和Performer类。)

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;
    
    @Mocked
    private Model model;

    @Tested
    private Performer performer;
    
    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0; 
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }
    
    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls; 
        }};
    }
    
    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls; 
        }
    }
    
    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

In this example, you can see in lines from 15 to 18 that we’re preparing an expectation for every test so that model.getInfo() always returns “foo” and for collaborator.collaborate() to always expect “foo” as the argument and returning true. We put the minTimes = 0 statement so no fails appear when not actually using them in tests.

在这个例子中,你可以看到从第15行到第18行,我们为每个测试准备了一个期望值,以便model.getInfo()总是返回“foo”,并且为collaborator.collaborate()总是期望“foo”作为参数并且返回。我们把minTimes = 0语句放在那里,以便在测试中不实际使用它们时不会出现失败。

Also, we’ve created method verifyTrueCalls(int) to simplify verifications to the collaborator.receive(boolean) method when the passed argument is true.

此外,我们还创建了方法verifyTrueCalls(int),以简化对collaborator.receive(boolean)方法的验证,当传递的参数是true

Lastly, you can also create new types of specific expectations and verifications just extending any of Expectations or Verifications classes. Then you define a constructor if you need to configure the behavior and create a new instance of said type in a test as we do in lines from 33 to 43.

最后,你也可以创建新的特定期望和验证类型,只需扩展任何ExpectationsVerifications类。然后你定义一个构造函数,如果你需要配置行为并在测试中创建一个所述类型的新实例,就像我们在第33到43行所做的那样。

6. Conclusion

6.结论

With this installment of the JMockit series, we have touched on several advanced topics that will definitely help you with everyday mocking and testing.

通过这一期的JMockit系列,我们谈到了几个高级话题,这些话题肯定会对你的日常嘲弄和测试有所帮助。

We may do more articles on JMockit, so stay tuned to learn even more.

我们可能会做更多关于JMockit的文章,所以请继续关注,以了解更多。

And, as always, the full implementation of this tutorial can be found over on GitHub.

而且,像往常一样,本教程的完整实现可以在GitHub上找到

6.1. Articles in the Series

6.1.丛书中的文章

All articles of the series:

该系列的所有文章。