概述
在这篇文章中,我们将深入探讨JMockit的高级用法,涉及的内容包括:
- 模拟(或MockUp API)
- 如何仅使用一个模拟对象mock多个接口
- 如何重用期望和验证
对于JMockit的基础知识,您可以参考系列文章中的其他内容,底部会提供相关链接。
2. Maven依赖
首先,我们需要在项目中添加JMockit的依赖:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49</version>
</dependency>
接下来,我们继续看示例。
3. 保护方法的模拟
有时,我们需要模拟受保护的方法。在JMockit中,我们可以使用MockUp API来修改受保护方法的真正实现。
所有示例都将针对以下类进行演示,并假设我们在测试类中使用与第一个相同的配置(以避免重复代码):
public class AdvancedCollaborator {
int i;
private int privateField = 5;
// default constructor omitted
public AdvancedCollaborator(String string) throws Exception{
i = string.length();
}
public String methodThatCallsProtectedMethod(int i) {
return protectedMethod() + i;
}
public int methodThatReturnsThePrivateField() {
return privateField;
}
private String protectedMethod() {
return "default:";
}
class InnerAdvancedCollaborator {...}
}
JMockit的MockUp API提供了创建模拟实现(或称为“mock-up”)的支持。通常,mock-up只针对要模拟的类中的几个方法和/或构造函数,而保留其他大部分方法和构造函数不变。JMockit支持模拟受保护的方法。
让我们看看如何使用MockUp API重新定义protectedMethod()
:
public class AdvancedCollaboratorIntegrationTest {
@Tested
private AdvancedCollaborator mock;
@Test
public void testToMockUpProtectedMethod() {
new MockUp<AdvancedCollaborator>() {
@Mock
private String protectedMethod() {
return "mocked: ";
}
};
String res = mock.methodThatCallsProtectedMethod(1);
assertEquals("mocked: 1", res);
}
}
在这个例子中,我们在匹配签名的方法上使用了@Mock
注解来定义一个新的AdvancedCollaborator
类的mock-up。此后,对该方法的调用将被委托给我们的模拟版本。
我们还可以使用它来简化测试,模拟需要特定参数或配置的类的构造函数:
@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);
}
在这个示例中,可以看到为了模拟构造函数,我们需要模拟$init
方法。我们可以传递一个类型为Invocation
的额外参数,以便访问关于模拟方法调用的信息,包括调用所作用的对象。
4. 私有字段的模拟
通常不建议直接测试私有字段,因为它们是类的内部细节。但在处理遗留代码时,有时仍然需要这样做。
在JMockit中,我们可以使用@Injectable
注解来模拟我们的私有字段。
设置私有字段:
@Test
public void testToSetPrivateFieldDirectly(@Injectable("10") int privateField){
assertEquals(10, privateField);
}
获取字段:
@Test
public void testToGetPrivateFieldDirectly(){
assertEquals(5, mock.methodThatReturnsThePrivateField());
}
5. 一次模拟多个接口
假设我们要测试一个尚未实现但肯定将实现多个接口的类。
通常,在实现之前无法测试此类,但使用JMockit,我们可以在实现之前提前准备测试,通过一个模拟对象mock多个接口。
这可以通过使用泛型并定义一个扩展多个接口的类型来实现。这个泛型类型可以应用于整个测试类,也可以应用于单个测试方法。
例如,我们将为List
和Comparator
接口创建一个模拟:
public class AdvancedCollaboratorIntegrationTest
interface IList<T> extends List<T> {}
interface IComparator extends Comparator<Integer>, Serializable {}
static class MultiMock {
IList<?> get() { return null; }
IComparator compareTo() { return null; }
}
@Test
public void testMultipleInterfacesWholeTest(@Mocked MultiMock multiMock) {
new Expectations() {
{
multiMock.get(); result = null;
multiMock.compareTo(); result = null;
}
};
assertNull(multiMock.get());
assertNull(multiMock.compareTo());
}
}
如图所示,我们在测试方法上定义了一个新的静态MultiMock
类。这样,MultiMock
就会作为类型存在,我们可以使用JMockit的@Mocked
注解创建它的模拟。
如果需要在方法中使用多接口模拟,可以在方法签名上定义@Mocked
注解,并将模拟作为测试方法的参数传递。
6. 重用期望和验证
在测试类的过程中,可能会遇到反复使用期望和验证的情况。为了简化这一点,我们可以轻松地重用它们。
我们将通过一个例子来解释(使用我们在JMockit 101文章中提到的Model
、Collaborator
和Performer
类):
public class ReusingIntegrationTest {
@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);
}
}
在这个例子中,我们在setup()
方法中为每个测试准备了期望,确保model.getInfo()
始终返回"foo"
,而collaborator.collaborate()
总是期待"foo"
作为参数并返回true
。我们使用minTimes = 0
语句是为了在测试中不实际使用它们时不会出现失败。
此外,我们还创建了verifyTrueCalls(int)
方法,以简化对collaborator.receive(boolean)
方法的验证,当传递的参数为true
时。
最后,我们还可以通过扩展任何期望或验证类创建特定类型的期望和验证。然后,如果需要配置行为,我们可以定义构造函数,并在测试中创建此类的新实例。
7. 总结
通过本篇JMockit系列文章,我们探讨了一些高级主题,这些无疑将帮助我们日常的模拟和测试工作。
我们可能还会有关于JMockit的更多文章,请关注以学习更多内容。
一如既往,本教程的完整实现可在GitHub上找到。
7.1. 系列文章
系列文章的所有内容: