You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mock对象未复现BillItems类异常抛出行为的原因咨询

问题:Mockito模拟实体类时未触发预期异常,改用真实对象后正常

我是JUnit测试新手,正在编写异常处理测试。最初使用mock(BillItems.class)模拟BillItems对象,以为它会复现实体类的行为,但运行测试时发现Mock对象与真实对象行为存在差异:调用setQuantity(-4)未触发预期的IllegalStateException。将BillItems改为通过new BillItems()创建实体对象后,测试正常通过。想咨询该问题的原因。

实体类

public class BillItems implements Serializable {

    private MenuItem menuItem;
    private int quantity;
    private String orderTime;


    public BillItems(){};
    public BillItems(MenuItem menuItem,int quantity,String orderTime){
        this.menuItem = menuItem;
        this.quantity = quantity;
        this.orderTime = orderTime;
    }

    public MenuItem getMenuItem() {
        return menuItem;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setMenuItem(MenuItem menuItem) {
        this.menuItem = menuItem;
    }

    public String getOrderTime() {
        return orderTime;
    }

    public void setOrderTime(String orderTime) {
        this.orderTime = orderTime;
    }

    public void setQuantity(int quantity) {
        if(quantity<0){   // 此处会抛出异常
            throw new IllegalStateException("Bill item cannot smaller than 0 !!!");
        }else {
            this.quantity = quantity;
        }
    }
}

使用Mock的JUnit测试代码

@Test
public void whenAddNewMenuItemsToBill_throwIllegalStateException_ifQuantityIsSmallerThanZero(){
    DailyMenu dailyMenu = mock(DailyMenu.class);
    BillItems billItems = mock(BillItems.class); // 问题所在
    MenuItem menuItem =  mock(MenuItem.class);
    List<BillItems> billItemsList = mock(ArrayList.class);

    menuPrinter.printMenu(dailyMenu);
    verify(menuPrinter).printMenu(dailyMenu);

    billItems.setMenuItem(menuItem);

    IllegalStateException exception = assertThrows(IllegalStateException.class,()->{
        billItems.setQuantity(-4);
    });
    assertTrue(exception.getMessage().contains("Bill item cannot smaller than 0 !!!"));
}

报错信息

org.opentest4j.AssertionFailedError: Expected java.lang.IllegalStateException to be thrown, but nothing was thrown.

    at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
    at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
    at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
    at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3083)
    at tests.bill.BillItemTest.whenAddNewMenuItemsToBill_throwIllegalStateException_ifQuantityIsSmallerThanZero(BillItemTest.java:69)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79)
    at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85)
    at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
    at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)


Process finished with exit code -1

修改后通过的测试代码

@Test
public void whenAddNewMenuItemsToBill_throwIllegalStateException_ifQuantityIsSmallerThanZero(){
    DailyMenu dailyMenu = mock(DailyMenu.class);
    BillItems billItems = new BillItems(); // 此处修改
    MenuItem menuItem =  mock(MenuItem.class);
    List<BillItems> billItemsList = mock(ArrayList.class);

    menuPrinter.printMenu(dailyMenu);
    verify(menuPrinter).printMenu(dailyMenu);

    billItems.setMenuItem(menuItem);

    IllegalStateException exception = assertThrows(IllegalStateException.class,()->{
        billItems.setQuantity(-4);
    });
    assertTrue(exception.getMessage().contains("Bill item cannot smaller than 0 !!!"));
}

测试结果

Process finished with exit code 0

原因解析

Mockito创建的Mock对象本质是原类的动态代理实例,默认不会执行原类中定义的任何方法逻辑:

  • 对于有返回值的方法,默认返回对应类型的默认值(比如int返回0,对象返回null);
  • 对于无返回值的void方法,默认什么也不做,直接跳过执行。

所以当你调用Mock对象的setQuantity(-4)时,并没有触发原BillItems类中判断数量是否小于0的逻辑,自然不会抛出IllegalStateException。

而通过new BillItems()创建的是真实的实体对象,调用它的方法时会完整执行原类中编写的业务逻辑,因此传入负数数量时会触发异常,测试也就正常通过了。

如果确实需要保留原类部分方法的逻辑同时使用Mock,可以使用Mockito.spy()方法创建间谍对象,但对于测试实体类自身业务逻辑的场景,直接使用真实对象是更合适的选择。


内容的提问来源于stack exchange,提问作者David Moore

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 18:50:32