Jmockit使用笔记_增加代码覆盖率_覆盖try catch_使用new MockUp私有方法

本文介绍如何使用JMockit 1.38版本进行单元测试,包括模拟异常处理、私有方法及new对象方法的Mock操作。

覆盖catch,模拟new对象方法及私有方法处理

测试案例的Jmockit版本
<dependency>
    <groupId>org.jmockit</groupId>
    <artifactId>jmockit</artifactId>
    <version>1.38</version>
    <scope>test</scope>
</dependency>
覆盖try catch中的异常处理逻辑
/**
 * 业务代码中进行了异常处理
 */
@Override
public void catchExceptionService() {
    try {
        List<String> queryResultList = myMapper.queryAllList();
    } catch (Exception e) {
        log.error("查询数据集合时发生异常:{}", e);
    }
}

测试案例模拟抛出异常,覆盖catch中的代码

/**
 * 测试案例
 */
@Test
public void catchExceptionService_test() {
    new Expectations() {
        {	
            // 让该方法被调用时抛出异常
            myMapper.queryAllList();
            result = new RuntimeException();
        }
    };
    myServicempl.catchExceptionService();
}
使用new MockUp模拟被测试代码中的私有方法
/**
 * @author: xinruoxiangyang9010
 * 业务代码:getOrderId()方法中调用了本类中的私有方法
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public String getOrderId() {
        List<String> orderIds = new ArrayList<>();
        orderIds.add("01");
        String productName = getProductName(orderIds);
        // 以下可能会用productName做一些其他的业务逻辑
        // .... ....
        return "OrderXX";
    }

    private String getProductName(List<String> orderIdList) {
        // 以下两行用于测试在mock时是否会调用真实代码逻辑
        orderIdList.add("readId1");
        orderIdList.add("readId2");
        // 返回结果
        return "Product01";
    }
}

对于单测案例来说,我们应该只关注 getOrderId()代码体中的内容,而避免受getProductName()方法的结果而影响测试案例结果,所以将getProductName()方法mock掉

/**
 * @author: xinruoxiangyang9010
 * 测试案例:被测试的方法:orderService.getOrderId()
 */
@RunWith(JMockit.class)
public class OrderServiceImplTest {

    @Tested
    private OrderServiceImpl orderService;

    @Test
    public void getOrderId() {
        new MockUp<OrderServiceImpl>() {
            @Mock
            private String getProductName(List<String> orderIdList){
                return "mock01";
            }
        };
        String orderId = orderService.getOrderId();
        Assert.assertEquals("OrderXX", orderId);
    }
}

可以通过断点模式看到,当方法getProductName()被调用时,直接按照我们Mock的结果进行返回,并没有进入到真实的方法中

使用 @Mocked对new的对象方法进行Mock
/**
 * 业务代码1
 * OrderCheck对象是new出来的,而不是注入的
 * @return
 */
@Override
public String getOrderName() {
    String orderName = "A000001";
    OrderCheck orderCheck = new OrderCheck();
    orderCheck.checkOrderName(orderName);
    return orderName;
}
/**
 * @author: xinruoxiangyang9010
 * 业务代码2
 * 以下的逻辑比较简单,但是如果遇到比较复杂的逻辑就不好调用真实方法了
 */
public class OrderCheck {
    public void checkOrderName(String orderName) {
        if (StringUtils.isEmpty(orderName) || !orderName.startsWith("B")) {
            throw new RuntimeException("订单名称校验异常");
        }
    }
}

与上面的案例类似, 当不想测试结果受checkOrderName(String orderName)方法影响时,将他Mock掉

/**
 * @author: xinruoxiangyang9010
 * 测试案例
 */
@RunWith(JMockit.class)
public class OrderServiceImplTest {
    @Mocked
    private OrderCheck orderCheck;
    @Tested
    private OrderServiceImpl orderService;

    @Test
    public void getOrderName_test(){
        new Expectations() {
            {
                orderCheck.checkOrderName(anyString);
                result = null;
            }
        };
        String orderName = orderService.getOrderName();
        Assert.assertEquals("A000001", orderName);
    }

}
<think>我们使用JMockitMockUp功能来覆盖公共方法中调用的私有方法。 核心思路:创建一个针对目标类的MockUp,并在其中定义与私有方法签名相同的@Mock方法,这样当公共方法调用私有方法时,实际上会执行我们Mock的私有方法。 注意:虽然我们覆盖的是私有方法,但需要在MockUp类中声明一个完全相同的私有方法(包括方法名、参数列表、返回类型),并使用@Mock注解标记。 下面通过一个示例说明: 假设有一个OrderService类,其中有一个公共方法processOrder(),它内部调用了一个私有方法calculateDiscount()。 我们希望在测试中覆盖这个私有方法的行为。 步骤: 1. 创建被测试类OrderService 2. 在测试类中,使用MockUp覆盖OrderService的私有方法calculateDiscount 3. 调用公共方法processOrder,验证其行为是否符合我们Mock的私有方法的预期 示例代码如下:</think>### 使用 Mockup 覆盖公共方法中调用的私有方法 在单元测试中,当需要覆盖公共方法内部调用的私有方法时,JMockit 的 `MockUp` 机制是最直接的解决方案。以下是详细实现步骤和示例: --- #### 核心原理 通过创建目标类的 `MockUp` 子类,使用 `@Mock` 注解覆盖私有方法。当公共方法调用该私有方法时,JMockit 会拦截调用并执行你预设的模拟逻辑[^1][^2]。 --- ### 实现步骤(以订单服务为例) #### 1. 定义被测试类 ```java public class OrderService { // 公共方法调用私有方法 public BigDecimal processOrder(Order order) { BigDecimal discount = calculateDiscount(order); // 调用私有方法 return order.getAmount().multiply(discount); } // 需要覆盖私有方法 private BigDecimal calculateDiscount(Order order) { // 实际业务逻辑(测试中需要跳过) return new BigDecimal("0.9"); } } ``` #### 2. 创建 Mockup 覆盖私有方法 ```java @Test public void testProcessOrderWithMockedPrivateMethod() { // 创建被测试实例 OrderService service = new OrderService(); // 覆盖私有方法 calculateDiscount new MockUp<OrderService>(service) { @Mock // 必须使用@Mock注解 private BigDecimal calculateDiscount(Order order) { // 返回预设的测试值 return new BigDecimal("0.75"); } }; // 调用公共方法(内部会调用已被覆盖私有方法) Order testOrder = new Order(new BigDecimal("100.00")); BigDecimal result = service.processOrder(testOrder); // 验证结果是否符合模拟逻辑 assertEquals(75.0, result.doubleValue(), 0.001); // 100 * 0.75 = 75 } ``` --- ### 关键细节说明 1. **方法签名匹配** 模拟方法必须与原始方法完全一致: - 相同的方法名 `calculateDiscount` - 相同的参数类型 `(Order order)` - 相同的返回类型 `BigDecimal` - 相同的访问修饰符 `private` 2. **作用域控制** Mockup 仅影响当前测试实例: ```java // 只mock特定实例,不影响其他实例 OrderService service1 = new OrderService(); OrderService service2 = new OrderService(); new MockUp<OrderService>(service1) { // 只对service1生效 @Mock private BigDecimal calculateDiscount(...) {...} }; ``` 3. **验证方法调用** 使用 `Verifications` 验证私有方法是否被调用: ```java new MockUp<OrderService>(service) { @Mock private BigDecimal calculateDiscount(Order order) { return BigDecimal.ONE; // 返回100%折扣 } }; service.processOrder(testOrder); // 验证私有方法调用 new Verifications() {{ // 通过反射验证私有方法调用 Deencapsulation.invoke(service, "calculateDiscount", testOrder); times = 1; }}; ``` 4. **模拟异常场景** 强制私有方法抛出异常: ```java new MockUp<OrderService>() { @Mock private BigDecimal calculateDiscount(Order order) { throw new DiscountCalculationException("测试异常"); } }; // 验证公共方法是否正确处理异常 assertThrows(DiscountCalculationException.class, () -> service.processOrder(testOrder)); ``` --- ### 完整测试场景示例 #### 场景:根据用户等级计算动态折扣 ```java @Test public void testUserLevelDiscount() { OrderService service = new OrderService(); Order goldOrder = new Order(new User("VIP"), new BigDecimal("200")); // 覆盖私有折扣计算方法 new MockUp<OrderService>(service) { @Mock private BigDecimal calculateDiscount(Order order) { // 根据用户级别返回不同折扣 return order.getUser().isVIP() ? new BigDecimal("0.8") : new BigDecimal("0.95"); } }; BigDecimal result = service.processOrder(goldOrder); assertEquals(160.0, result.doubleValue(), 0.001); // 200 * 0.8 = 160 } ``` --- ### 常见问题解决 **Q: 出现 `MethodNotFoundException` 怎么办?** A: 检查方法签名是否完全匹配,包括参数类型和返回类型。Java 编译器会修改私有方法名,确保使用原始方法名[^1]。 **Q: 如何同时覆盖多个私有方法?** A: 在同一个 `MockUp` 类中定义多个 `@Mock` 方法: ```java new MockUp<OrderService>() { @Mock private BigDecimal calculateDiscount(...) {...} @Mock private void logOperation(...) {...} // 覆盖另一个私有方法 }; ``` **Q: Spring 管理的 Bean 如何测试?** A: 在 `@SpringBootTest` 中注入实例后创建 Mockup: ```java @Autowired private OrderService service; @Test public void testWithSpring() { new MockUp<OrderService>(service) { @Mock private BigDecimal calculateDiscount(...) {...} }; // 测试逻辑 } ``` --- ### 最佳实践 1. **优先公共接口测试** 通过公共方法 `processOrder()` 测试私有方法 `calculateDiscount()`,避免过度聚焦内部实现[^3] 2. **组合使用技术** ```java // Mockup + 反射设置私有字段 new MockUp<OrderService>(service) { @Mock private BigDecimal calculateDiscount(...) {...} }; // 修改私有配置字段 Deencapsulation.setField(service, "discountThreshold", 500.00); ``` 3. **生命周期管理** 使用 `try-with-resources` 自动清理: ```java try (MockUp<OrderService> mock = new MockUp<>(service) { @Mock private BigDecimal calculateDiscount(...) { return BigDecimal.ONE; } }) { // 测试代码 } // 自动恢复原始方法 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值