1. 概述
在这个简短的教程中,我们将探讨使用Mockito和PowerMock在Java中有效地模拟构造函数的各种选项。
2. 使用PowerMock模拟构造函数
在Mockito 3.3及更低版本中,无法模拟构造函数或静态方法。在这种情况下,PowerMock这样的库提供了额外功能,使我们能够控制构造函数的行为并协调它们之间的交互。
3. 模型
让我们用两个Java类模拟一个支付处理系统。我们将创建一个PaymentService
类,其中包含处理付款的逻辑,并提供两种构造函数:一种是参数化构造函数,用于指定付款方式;另一种是默认构造函数,带有备选模式:
public class PaymentService {
private final String paymentMode;
public PaymentService(String paymentMode) {
this.paymentMode = paymentMode;
}
public PaymentService() {
this.paymentMode = "Cash";
}
public String processPayment(){
return this.paymentMode;
}
}
PaymentProcessor
类依赖于PaymentService
来执行付款处理任务,并提供两个构造函数:一个用于默认设置,另一个用于自定义付款模式:
public class PaymentProcessor {
private final PaymentService paymentService;
public PaymentProcessor() {
this.paymentService = new PaymentService();
}
public PaymentProcessor(String paymentMode) {
this.paymentService = new PaymentService(paymentMode);
}
public String processPayment(){
return paymentService.processPayment();
}
}
4. 使用Mockito模拟默认构造函数
编写单元测试时,隔离要测试的代码至关重要。 构造函数通常会创建我们不想在测试中涉及的依赖。模拟构造函数允许我们将真实对象替换为模拟对象,确保我们测试的是特定单元的行为。
从Mockito 3.4及更高版本开始,我们可以访问mockConstruction()
方法。它允许我们模拟对象的构造。我们首先指定要模拟构造函数的类作为第一个参数。其次,我们提供一个形式为MockInitializer
回调函数的第二个参数。这个回调函数允许我们在构造过程中定义和操纵模拟对象的行为:
@Test
void whenConstructorInvokedWithInitializer_ThenMockObjectShouldBeCreated(){
try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class,(mock,context)-> {
when(mock.processPayment()).thenReturn("Credit");
})){
PaymentProcessor paymentProcessor = new PaymentProcessor();
Assertions.assertEquals(1,mockPaymentService.constructed().size());
Assertions.assertEquals("Credit", paymentProcessor.processPayment());
}
}
mockConstruction()
方法有多个重载版本,以适应不同的用例。在下面的例子中,我们没有使用MockInitializer
初始化模拟对象。我们验证构造函数被调用了一次,而没有初始化器确保构建的PaymentService
对象中的paymentMode
字段处于null
状态:
@Test
void whenConstructorInvokedWithoutInitializer_ThenMockObjectShouldBeCreatedWithNullFields(){
try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class)){
PaymentProcessor paymentProcessor = new PaymentProcessor();
Assertions.assertEquals(1,mockPaymentService.constructed().size());
Assertions.assertNull(paymentProcessor.processPayment());
}
}
5. 使用Mockito模拟参数化构造函数
在这个例子中,我们设置了MockInitializer
并调用了参数化构造函数。我们验证确实创建了一个精确的模拟对象,并且在初始化时具有期望的值:
@Test
void whenConstructorInvokedWithParameters_ThenMockObjectShouldBeCreated(){
try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class,(mock, context) -> {
when(mock.processPayment()).thenReturn("Credit");
})){
PaymentProcessor paymentProcessor = new PaymentProcessor("Debit");
Assertions.assertEquals(1,mockPaymentService.constructed().size());
Assertions.assertEquals("Credit", paymentProcessor.processPayment());
}
}
6. 模拟构造函数的范围
在Java中,我们可以使用try-with-resources语句来限制模拟对象的范围。在这个块内,对指定类的公共构造函数的任何调用都会创建模拟对象。当块外的任何地方调用真正的构造函数时,它会被调用。
以下示例中,我们没有定义任何初始化器,并多次调用默认和参数化构造函数。然后,模拟行为在构造后定义:
我们验证确实创建了三个模拟对象,并且遵循我们的预定义模拟行为:
@Test
void whenMultipleConstructorsInvoked_ThenMultipleMockObjectsShouldBeCreated(){
try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class)){
PaymentProcessor paymentProcessor = new PaymentProcessor();
PaymentProcessor secondPaymentProcessor = new PaymentProcessor();
PaymentProcessor thirdPaymentProcessor = new PaymentProcessor("Debit");
when(mockPaymentService.constructed().get(0).processPayment()).thenReturn("Credit");
when(mockPaymentService.constructed().get(1).processPayment()).thenReturn("Online Banking");
Assertions.assertEquals(3,mockPaymentService.constructed().size());
Assertions.assertEquals("Credit", paymentProcessor.processPayment());
Assertions.assertEquals("Online Banking", secondPaymentProcessor.processPayment());
Assertions.assertNull(thirdPaymentProcessor.processPayment());
}
}
7. 依赖注入与构造函数模拟
当我们使用依赖注入时,可以直接传递模拟对象,无需模拟构造函数。通过这种方法,我们可以在测试类之前模拟依赖项,从而消除模拟构造函数的需要。
让我们在PaymentProcessor
类中添加第三个构造函数,将PaymentService
作为依赖项注入:
public PaymentProcessor(PaymentService paymentService) {
this.paymentService = paymentService;
}
我们已经解耦了PaymentProcessor
类的依赖性,使其可以独立测试,并且可以通过模拟来控制依赖项的行为,如下所示:
@Test
void whenDependencyInjectionIsUsed_ThenMockObjectShouldBeCreated(){
PaymentService mockPaymentService = Mockito.mock(PaymentService.class);
when(mockPaymentService.processPayment()).thenReturn("Online Banking");
PaymentProcessor paymentProcessor = new PaymentProcessor(mockPaymentService);
Assertions.assertEquals("Online Banking", paymentProcessor.processPayment());
}
然而,在源代码中无法控制依赖管理的情况下,特别是当依赖注入不可用时,mockConstruction()
仍然是有效模拟构造函数的有用工具。
8. 总结
本文简要介绍了通过Mockito和PowerMock模拟构造函数的不同方法。我们也讨论了在可能的情况下优先考虑依赖注入的优点。
如往常一样,代码可在GitHub上找到。