Mock 实战

 目录

1. 初始化

2. @InjectMocks  案例 

3. 设置测试桩(Stubbing)

4. Stubbing连缀调用

5. Mock方法的默认值

6. 参数匹配器(matchers)

7. 行为测试

 8. Answer

9. 有返回值的方法demo

10. void方法demo

11. void方法的mock

添加依赖

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>

1. 初始化

1)注解
@RunWith(MockitoJUnitRunner.class)
@Mock相当于:NeedMockClass mockInstatnce = Mockito.mock(NeedMockClass.class);
@Spy相当于:NeedMockClass spyInstatnce = Mockito.spy(new NeedMockClass());
@InjectMocks相当于:被测试类上标记@InjectMocks,Mockito就会实例化该类,并将标记@Mock、@Spy注解的属性值注入到被测试类中。

@InjectMocks的注入顺序:
1、 如果这里的TargetClass中没有显示定义构造方法,Mockito会调用默认构造函数实例化对象,然后依次寻找setter 方法 或 属性(按Mock对象的类型或名称匹配)注入@Mock对象;
2、如果TargetClass中显式定义了有参数的构造函数,那么 就不再寻找setter 方法和 属性注入, Mockito会选择参数个数最多的构造函数实例化并注入@Mock对象(这样可以尽可能注入多的属性);
3、有多个最大构造函数时,Mockito 究竟选择哪一个就混乱了,测试时应该避免这种情况的发生,很容易发生空指针。

2)类反射

2. @InjectMocks  案例 

@Service
public class MsgLogService implements IMsgLogService {
    @Resource
    private MsgLogMapper msgLogMapper;

    @Resource
    private IApiService apiService;

    @Override
    public void handle() {
        List<MsgLog> list = msgLogMapper.selectUnHandledMsg();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        for (MsgLog log : list) {
            if (StringUtils.isEmpty(log.getMsg())) {
                continue;
            }

            boolean res = apiService.putProfileToHBase(log.getMsg());
            if (res) {
                msgLogMapper.updateHandleStatus(log);
            }
        }
    }
}
/**
MsgLogService 用@InjectMocks标识,且MsgLogMapper、IApiService用@Mock标识
则MsgLogService内的 MsgLogMapper、apiService 就自动注入
*/
@RunWith(MockitoJUnitRunner.class)
public class MsgLogServiceTest {
    @InjectMocks
    private MsgLogService msgLogService;

    @Mock
    private MsgLogMapper msgLogMapper;

    @Mock
    private IApiService apiService;

    public MsgLogServiceTest() {}

    @Test
    public void handle_request_times_0_01() {
        when(msgLogMapper.selectUnHandledMsg()).thenReturn(null);
        msgLogService.handle();
        verify(apiService, times(0)).putProfileToHBase(anyString());
        verify(msgLogMapper, times(0)).updateHandleStatus(any());
    }
}

3. 设置测试桩(Stubbing)

when(mockMapper.insert(any())).thenReturn(888);
when(mockMapper.insert(any())).thenThrow(new RuntimeException("db操作异常"));
when(mockService.methodReturnId(any(OrderInfo.class))).thenAnswer(demoAnswer);

doReturn(888).when(mockMapper).insert(any());

上面mock的方法都是有返回值的,为void函数设置桩用以下语法,因为编译器不喜欢void函数在括号内

doNothing().when(mockService).voidMethod(any(String.class), any(Integer.class));
doThrow(new RuntimeException("")).when(mockService).voidMethod(eq("ex"), eq(10001));
doAnswer(demoAnswer).when(mockService).methodVoid(any(OrderInfo.class));

4. Stubbing连缀调用

第一次调用返回1,第二次调用返回2,以下三种写法等价的:
when(mockService.addStr(anyString())).thenReturn("1").thenReturn("2");
when(mockService.addStr(anyString())).thenReturn("1", "2");
doReturn("1").doReturn("2").when(mockService).addStr(anyString());

String relt1 = mockService.addStr("x");
String relt2 = mockService.addStr("x");
String relt3 = mockService.addStr("x");

Assert.assertEquals("1", relt1);
Assert.assertEquals("2", relt2);
Assert.assertEquals("2", relt3); //后续调用一直返回2
第一次调用什么也不做,第二次调用抛出异常:
doNothing().doThrow(new RuntimeException("调用两次了")).when(mockService).methodVoid(any());
mockService.methodVoid(any());
try {
    mockService.methodVoid(any());
} catch (Exception e) {
    Assert.assertEquals("调用两次了", e.getMessage());
}
下面写法结果就变了,第二次stubbing覆盖第一次的:
when(mockService.addStr(anyString())).thenReturn("1");
when(mockService.addStr(anyString())).thenReturn("2");

String relt1 = mockService.addStr("x");
String relt2 = mockService.addStr("x");

Assert.assertEquals("2", relt1);
Assert.assertEquals("2", relt2);

5. Mock方法的默认值

Mock对象的方法未设置测试桩时,Mockito会返回方法返回类型的默认值,不会报错。mock 实例默认的会给所有的方法添加基本实现:返回 null 或空集合,或者 0 等基本类型的值。这取决于方法返回类型

List mockList = mock(List.class);
when(mockList.get(5)).thenReturn("hello");//打桩

Assert.assertEquals("hello", mockList.get(5));//打桩的情景返回设定值
Assert.assertEquals(null, mockList.get(10));//未打桩的情景不会报错,返回默认值

6. 参数匹配器(matchers)

any(): 任何参数
any(OrderInfo.class): 任何OrderInfo(开发中自定义的类)
anyString(): 任何字符串,等同于any(String.class)
eq(1): 具体值1

InvokeService类中,要被mock的方法
public Integer targetMethod01(String param01, Integer param02) {
    return 1;
}

测试类TargetClassTest中:
@Mock
private InvokeService mockService;

@Test
public void dmeoTest() {
    when(mockService.targetMethod01(any(), any())).thenReturn(666);
    when(mockService.targetMethod01(any(String.class), anyInt())).thenReturn(666);
    when(mockService.targetMethod01("demo", 1)).thenReturn(666);
    when(mockService.targetMethod01(eq("demo"), eq(1))).thenReturn(666);

	// When using matchers, all arguments have to be provided by matchers.
    //上面都是正确的,下面的写法,单测执行时会报错
    when(mockService.targetMethod01(eq("demo"), 1)).thenReturn(666);
}

7. 行为测试

一旦使用 mock() 或@Mock生成模拟对象,意味着 Mockito 会记录着这个模拟对象调用了什么方法,还有调用了多少次、调用的顺序等。最后由用户决定是否需要进行验证。

  • times(x):mock方法被调用x次
  • never():从未被调用过,等价于times(0)
  • atLeast(x):至少调用过x次
  • atLeastOnce():至少调用过1次,等价于atLeast(1)
  • atMost(x):最多调用过x次 
  • verify 内部跟踪了所有的方法调用和参数的调用情况,然后会返回一个结果,说明是否通过。verify 也可以像 when 那样使用参数匹配器。 
    目标类TargetClass中:
    @Autowired
    private InvokeService invokeService;
    
    public void invokeTimes() {
    	invokeService.addStr("1");
    	invokeService.addStr("2");
    	invokeService.addStr("2");
    }
    
    测试类TargetClassTest中:
    @InjectMocks
    TargetClass targetClass;
    
    @Mock
    private InvokeService mockService;
    
    @Test
    public void testTimes() {
    	targetClass.invokeTimes();
    
    	verify(mockService).addStr(eq("1"));//times()省略的话,默认验证调用一次
    	verify(mockService, times(1)).addStr(eq("1"));
    	verify(mockService, times(1)).addStr("1");
    
    	verify(mockService, times(2)).addStr("2");
    	verify(mockService, atLeastOnce()).addStr("1");
    	verify(mockService, atMost(2)).addStr("2");
    
    	verify(mockService, never()).addStr("0");
    }

    验证Mock方法的调用顺序

目标类TargetClass中:
@Autowired
private InvokeService invokeService;

public void addStr() {
    invokeService.add("str01");
    invokeService.add("str02");
}

测试类TargetClassTest中:
@InjectMocks
TargetClass targetClass;

@Mock
private InvokeService mockService;

@Test
public void testInOrder_verify() {
	InOrder inOrder = inOrder(mockService);
	targetClass.addStr();

	//验证调用顺序,若是调换两句,将会出错
	inOrder.verify(mockService).add("str01");
	inOrder.verify(mockService).add("str02");
}

 8. Answer

主要用来截获传递给mock方法的参数

目标类TargetClass中:
@Autowired
private InvokeService invokeService;

@Autowired
private InvokeMapper invokeMapper;

public int saveOrder() {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setUserName("sxd");
    orderInfo.setDealerId(62669);
    orderInfo.setOrderType(3);
    int result = invokeMapper.insert(orderInfo);
    return result;
}


InvokeMapper中:
int insert(OrderInfo orderInfo);


测试类TargetClassTest中:
@InjectMocks
TargetClass targetClass;

@Mock
private InvokeService mockService;

@Mock
private InvokeMapper mockMapper;

//定义一个Answer类,用于截获mock方法入参
class DemoAnswer implements Answer<Object> {
    Object[] params;

    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
        params = invocationOnMock.getArguments(); //方法入参
        return 666; //方法返回值
    }

    public Object[] getParams() {
        return params;
    }
}

@Test
public void test_insertOrder_param_by_Answer() throws Exception {
    DemoAnswer demoAnswer = new DemoAnswer();
    doAnswer(demoAnswer).when(mockMapper).insert(any(OrderInfo.class));
    
    int result = targetClass.saveOrder();
    Assert.assertEquals(666, result);
    
    OrderInfo saveOrder = (OrderInfo) demoAnswer.getParams()[0];
    Assert.assertEquals(3, saveOrder.getOrderType().intValue());
}

9. 有返回值的方法demo

InvokeService类中,需要mock的方法:
public Integer methodReturnId(OrderInfo orderInfo) throws Exception {
    return orderInfo.getId();
}

测试类TargetClassTest中:
@Mock
private InvokeService mockService;

@Test
public void testAnswer_when_method_unvoid() throws Exception {
    Answer<Integer> demoAnswer = new Answer<Integer>() {
        @Override
        public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
            OrderInfo orderInfo = (OrderInfo) invocationOnMock.getArguments()[0]; //methodReturnId方法的入参

            //orderInfo.setId(2);//截获并自定义入参
            if (orderInfo.getId() > 3) {
                throw new RuntimeException("大于3了");
            }

            return 666;//自定义methodReturnId方法的返回值
        }
    };

    //mock的方法是非void时,以下三种写法都可以
    //doAnswer(answer).when(mockService).methodReturnId(any(OrderInfo.class)); //doAnswer:执行demoAnswer的answer方法

    //when(mockService.methodReturnId(any(OrderInfo.class))).then(demoAnswer); //then(answer): 执行demoAnswer的answer方法

    when(mockService.methodReturnId(any(OrderInfo.class))).thenAnswer(demoAnswer); //thenAnswer(answer): 执行demoAnswer的answer方法

    OrderInfo paramOrder = new OrderInfo() {{
        setId(3);
    }};

    Integer result = mockService.methodReturnId(paramOrder);
    Assert.assertEquals(666, result.intValue());

    OrderInfo paramOrder02 = new OrderInfo() {{
        setId(4);
    }};

    try {
        mockService.methodReturnId(paramOrder02);
    } catch (Exception e) {
        Assert.assertEquals("大于3了", e.getMessage());
    }
}

10. void方法demo

InvokeService中,需要mock的方法:
public void methodVoid(OrderInfo orderInfo) {

}

测试类中:
@Mock
private InvokeService mockService;

@Test
public void testAnswer_when_method_void() throws Exception {
    Answer<Integer> demoAnswer = new Answer<Integer>() {
        @Override
        public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
            OrderInfo orderInfo = (OrderInfo) invocationOnMock.getArguments()[0];//methodReturnId方法的入参
            //orderInfo.setId(2);
            if (orderInfo.getId() > 3) {
                throw new RuntimeException("大于3了");
            }
            return null;
        }
    };

    //void方法时,只能这样写
    doAnswer(demoAnswer).when(mockService).methodVoid(any(OrderInfo.class));//doAnswer:执行demoAnswer的answer方法

    mockService.methodVoid(new OrderInfo() {{
        setId(3);
    }});
}

11. void方法的mock

doNothing().when(mockService).voidMethod(anyString(), any(Integer.class)); //什么也不做
doThrow(new RuntimeException("")).when(mockService).voidMethod(eq("ex"), eq(10001)); //抛出指定异常
doNothing().doThrow(new RuntimeException("")).when(mockService).voidMethod(anyString(), any(Integer.class)); //第一次调用什么也不做,第二次调用抛异常

相关文章 Mockito 常见操作https://blog.csdn.net/sugelachao/article/details/124277623

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值