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
相关产品推荐
相关产品推荐

