在spring中使用mockito


背景

最近在做的一个双11的业务项目中,对业务系统中做一些复杂业务层类的单元测试时,一方面,注入bean的会是个很头大的事情,因为bean与bean之间有各种依赖关系,一些bean是通过注解的方式实现,一些依赖外部的bean是通过xml来配置的(如何在webx中维护test下配置文件,可以另外讲了);另一方面,对依赖的服务,常常希望它呈现不同的状态,用mock方式最简便不过了。

mock的框架有好多(Mockito, EasyMock, PowerMock等),在支持的特性和语法使用上有些许差别,总的来说mockito最较为常见,语法直观文档较多,常用场景够支持了,这里就用的mockito为例。Mockito官方文档

怎么mock有很多层次的理解,我主要举下例子:

  • 直接创建mock对象,并设置对象方法调用时的预期返回值
  • 被测bean A中,mock A依赖的bean B的行为动作(在spring框架中做待测类的mock时常用)


使用方式

1. 依赖包:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>2.0.2-beta</version>
</dependency>

2. 创建mock对象,并设置对象方法调用时的预期返回值

//创建mock对象,参数可以是类,也可以是接口  
List<String> list = mock(List.class);  
//设置方法的预期返回值  
when(list.get(0)).thenReturn("helloworld");  
//验证方法调用(是否调用了get(0))  
verify(list).get(0);  

3. 被测bean A中,mock A依赖的bean B的行为动作

结论:对于被mock的bean B用@Mocks标注;对被测类bean A自己,用@InjectMocks标注

解释:

  1. bean被标记了@InjectMocks , 在before方法中执行 MockitoAnnotations.initMocks(this)的时候,会将标记了@Mock或@Spy的属性注入到该bean 中。
  2. 如果是@Mock, 那就是通常的方式,bean里面的provider完全被Mock实例替换,所有的调用都是针对Mock生成类的。
  3. 如果是bean加载又加@Spy , 那么对于定制了返回值的会调用Mock实例,否则会调用真实注入的属性。

另外行为模拟上有多种方式:

  1. 对于有返回值的方法,采用Mockito.when().thenReturn();
  2. 没有返回值时,即void方法,我们可以采用Mockito.doNothing().when().XXX();
  3. 抛异常的场景Mockito.doThrow().when().XXX();

同时,参数的构造提供anyLong()、anyInt()、anyBoolean()、any(XXX.class)。


一个实例

bean的依赖图如下,inventoryMsgService中需要mock三个bean的行为,其他表现为真实行为。

1

public class BaseTestCase {
    protected static ApplicationContext ac = null;

    private static String[] configXml = {
            "basecase.xml"
    };

    static {
        try {
            HSFEasyStarter.start("hsfunit", "1.4.9.6");
            ac = new ClassPathXmlApplicationContext(configXml);
        } catch (Exception e) {
            System.out.println("Error");
        }
    }
}


public class BomMsgUnitTest extends BaseTestCase{

   @InjectMocks
    private InventoryMsgService inventoryMsgService;
    @Mock
    private InventoryMsgOccupyOutCore inventoryMsgOccupyOutCore;
    @Mock
    private InventoryMsgReportCore inventoryMsgReportCore;
    @Mock
    private InventoryMsgManager inventoryMsgManager;

    @Before
    public void init(){
        inventoryMsgService = (InventoryMsgService)ac.getBean("inventoryMsgServiceImpl");
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testInventoryOcuppy(){
        //mock inventoryMsgOccupyOutCore.alloteMsgDetail
        when(inventoryMsgOccupyOutCore.alloteMsgDetail(any(InventoryMsgDetailDO.class), any(Boolean.class))).thenReturn(true);
        //mock inventoryMsgReportCore.getOrderReoprtStatus
        InventoryMsgResp resp = new InventoryMsgResp();
        resp.setStatus(InventoryMsgStatus.OCCUPY_SUCCESS.getType());
        when(inventoryMsgReportCore.getOrderReoprtStatus(anyLong(), anyLong(), anyInt(), anyBoolean())).thenReturn(resp);
        //mock inventoryMsgManager.updateMsgStatus(msgDO)
        Mockito.doNothing().when(inventoryMsgManager).updateMsgStatus(
                any(InventoryMsgDO.class));

        InventoryOrderMsgReq orderMsgReq = new InventoryOrderMsgReq();

       //...
        //入参数据构造
        //...

        boolean testResult = inventoryMsgService.allotInventory(orderMsgReq);

        verify(inventoryMsgOccupyOutCore.alloteMsgDetail(any(InventoryMsgDetailDO.class), any(Boolean.class)));
        verify(inventoryMsgReportCore.getOrderReoprtStatus(anyLong(), anyLong(), anyInt(), anyBoolean()));
        verify(inventoryMsgManager.updateMsgStatus(any(InventoryMsgDO.class)));

        Assert.assertTrue(testResult);
    }
}   


其他

将mock方式恰当地用在业务逻辑层的单元测试上,是能帮助构造很多异常场景用例的,比如项目组里前段时间发生的事务失效的线上问题,其实也是可以通过单元测试,mock 数据层的操作抛RuntimeException,来验证是否事务生效回滚,下次可以说下这个。


相关文章

SpringBoot与JUnit+Mockito 单元测试

Unit tests with Mockito - Tutorial

crystal /
Published under (CC) BY-NC-SA in categories 测试  tagged with mockito  单元测试