我是靠谱客的博主 着急枫叶,最近开发中收集的这篇文章主要介绍Mockito结合Junit单测使用,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Mockito

文章目录

    • Mockito
      • 前言
      • 简介
      • 依赖
      • 如何编写一个好的测试代码?
      • Mockito的局限性
      • 快速入门
        • 真实代码
        • 测试代码
      • 设置运行Mockito测试环境方式
      • 注入Mock对象
        • 什么是mock对象
        • 注入方式
      • 注入Spy对象
        • 什么是spy对象
        • 注入方式
      • @InjectMocks对象
        • 注入逻辑
      • 多层级mock依赖注入解决
        • 解决
        • Stub设置预设数据/逻辑
      • 参数匹配
      • Mock controller层请求进行测试
      • Springboot中进行JUnit Mockito单测
        • 依赖
        • 快速入门
    • 结尾

前言

文章为个人总结存在一些问题还请大佬指教。

简介

是一个单元测试的模拟框架。通过设置模拟类达到模拟数据测试的轻框架。

依赖

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

如何编写一个好的测试代码?

1.测试代码应该保持和生产代码紧凑

2.测试代码各个依赖不应该强耦合,一些外部链接可以模拟

3.覆盖各种情况的出现

4.不应该模拟自己不拥有的类,例:第三方的代码

5.不应该模拟全部,否则不能达到测试真实代码的目的

Mockito的局限性

2.x版本(java 6+)

1.不能模拟构造方法

2.不能模拟静态方法

1.x版本(java 5+)

1.不能模拟final类

2.不能模拟final方法

3.不能模拟构造方法

4.不能模拟静态方法

快速入门

真实代码

@RestController
public class UserController {
@Resource
private UserService userService;
public User insertUser(User user){
...
}

测试代码

@RunWith(MockitoJUnitRunner.class)
//设置运行环境
public class UserControllerTest {
@InjectMocks	//被测对象
private UserController controller;
@Mock
//注入模拟对象
private UserService userService;
@Test
//方法测试
public void insertUser() throws Exception {
User user = new User();
user.setId(5);
user.setUsername("张三");
user.setAge(18);
user.setPassword("567");
user.setTime(new Date());
//表示当userService执行insert()方法时什么都不做
doNothing().when(userService).insert(user);
controller.insertUser(user);
}
}

设置运行Mockito测试环境方式

1.@RunWith(MockitoJUnitRunner.class) 缺点在于当测试代码需要其他环境则无法进行

2.MockitoAnnotations.initMocks(this); 通常在@Before方法中指定,该方式可用于多个运行环境中测试

3.@Rule MockitoRule rule = MockitoJUnit.rule();

注入Mock对象

什么是mock对象

mock(模拟)对象,调用该对象的任何方法不会走真实方法逻辑,调用结果都相当于什么都不做,需自己通过stub来设置执行逻辑。

可以解决测试中不需要关心的/耦合强的/难以真实调用的对象方法,我们可以为它设置模拟数据返回。

例:1.代码中需要调用远程服务,而这里的逻辑不应该属于该单元测试中的范围,就可以模拟该远程服务对象返回模拟数据,保证正常测试执行。
2.又或是该测试代码我们不需要关心sql的执行情况,就可以模拟dao层对象返回测试数据

注入方式

1.@Mock

表明被注解对象是一个Mock(模拟)对象

2.mock(Class class);

UserService service = mock(UserService.class);

注入Spy对象

什么是spy对象

spy(监视)对象,与mock对象不同的是调用该对象会执行真实对象的代码逻辑,需自己通过stub来设置模拟逻辑。

注入方式

1.@Spy

表明被注解对象是一个Mock(模拟)对象

2.spy(Class class);

UserService service = spy(UserService.class);

@InjectMocks对象

会将其他的@Mock/@Spy修饰的对象注入到该注解修饰的对象。
此外使用该注解必须设置运行环境,例:MockitoAnnotations.initMocks(this);

注入逻辑

Mockito 将尝试仅通过构造函数注入、setter 注入或属性注入依次注入模拟。 如果以下任一策略失败,则 Mockito不会报告失败; 即必须自己提供依赖项。

构造函数注入;

选择最大的构造函数,然后使用仅在测试中声明的模拟来解析参数。 如果使用构造函数成功创建了对象,则Mockito 不会尝试其他策略。 Mockito 决定不破坏具有参数构造函数的对象。
注意:如果找不到参数,则传递 null。 如果需要不可模拟的类型,则不会发生构造函数注入。 在这些情况下,您必须自己满足依赖项。

属性设置器注入

mocks 将首先按类型解析,然后,如果有多个相同类型的属性,则通过属性名称和模拟名称的匹配。
注意1:如果你有相同类型的属性,最好用匹配的属性命名所有@Mock注解的字段,否则Mockito可能会混淆并且不会发生注入。
注意2:如果@InjectMocks 实例之前没有初始化并且有一个无参数构造函数,那么它将用这个构造函数初始化。

mock()/spy()注入

mocks 将首先按类型解析,然后,如果有多个相同类型的属性,则通过字段名称和模拟名称的匹配。
注意1:如果你有相同类型的字段,最好用匹配的字段命名所有@Mock注释的字段,否则Mockito可能会混淆并且不会发生注入。
注意2:如果@InjectMocks 实例之前没有初始化并且有一个无参数构造函数,那么它将用这个构造函数初始化。

多层级mock依赖注入解决

在日常开发中经常会出现这种情况:controllerA 注入了 serviceA,serviceA 注入了 daoA;

这种情况下如果我们想要在controllerA调用方法,然后当执行到daoA方法时进行模拟,mockito是无法完成的;

Mockito不支持为一个spy/mock注入另一个spy/mock。

解决

目前知道的解决方法仅有一个,有其他解决的方法希望大佬不吝赐教。

可以利用反射将serviceA的daoA属性换成mock的daoA属性。

@RunWith(MockitoJUnitRunner.class)
//设置运行环境
public class UserControllerTest {
@InjectMocks	//被测对象
private UserController controller;
@Spy
//注入模拟对象
private UserService userService;
@Mock
private UserMapper userMapper;
@Before
public void setup(){
ReflectionTestUtils.setField(userService,"userMapper", this.userMapper);
}
@Test
//方法测试
public void insertUser() throws Exception {
User user = new User();
user.setId(5);
user.setUsername("张三");
user.setAge(18);
user.setPassword("567");
user.setTime(new Date());
//表示当userService执行insert()方法时什么都不做
doNothing().when(userMapper).insert(user);
controller.insertUser(user);
}
}

Stub设置预设数据/逻辑

stub是为了让对象去执行的预设的一串逻辑,提供模拟的数据。

when(T methodCall)

表示当…,需要有参数,所以里面不能执行无返回值方法

thenReturn | Answer | Throw() | CallRealMethod

通常在when()后使用:when(list.get(0)).thenReturn(5); 表示当list执行了get(0)就返回5;

四个依次表示:然后返回 | 自定义逻辑返回 | 抛出个异常 | 回调真实方法

thenAnswer介绍

//使用自定义答案存根模拟的示例:
//Answer<T>:T返回值类型,Answer表示一个可获得Mock对象的接口
when(mock.someMethod(anyString())).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
}
});
//stream流式编程
when(mock.someMethod(anyString())).thenAnswer(x->{
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
})

doReturn | Answer | Throw() | CallRealMethod

与thenXXX() 区别:

1.执行链路不一样
doReturn(5).when(list.get(0));
when(list.get(0)).thenReturn(5);
2.doXXX()可以作用于无返回值的方法
doReturn(5).when(list).add(5);	//不报错
when(list.add(5)).thenReturn(5);	//编译报错
3.对于spy对象建议使用doXXX(),因为spy对象执行方法是真实方法逻辑,使用when()会真实调用该方法而产生不希望出现的错误,如空指针异常等;
而我们通常使用stub就是为了预设数据不走真实方法,doXXX就是这样的逻辑。

参数匹配

any():匹配任何参数;anyInt():匹配任意int参数 …

when(list.get(anyInt())).doReturn(5);

Mock controller层请求进行测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class CustomerOrderControllerTest {
@Autowired
private CustomerOrderController controller;
private MockMvc mockMvc;
private WebApplicationContext applicationContext;
@Before
public void setupMockMvc(){
//参数指定controller则指对该controller获取其相关上下文
//mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
//读取整个web上下文构造Mock
mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
}
@Test
public void exceptionOrderBack() throws Exception {
String json = "{}";
mockMvc.perform(MockMvcRequestBuilders.post("/v1/customercenter/customerOrder/exceptionOrderBack")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(json.getBytes())
).andExpect(MockMvcResultMatchers.status().isOk());
}

Springboot中进行JUnit Mockito单测

依赖

<!-- 该依赖中整合了mockito -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

快速入门

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserControllerTest {
@Autowired
private UserController controller;
@SpyBean
private UserService userService;
@MockBean
private UserMapper userMapper;
@Before
public void setupMockMvc(){
ReflectionTestUtils.setField(userService,"userMapper", this.userMapper);
}
@Test
public void insertUser() throws Exception {
when(userMapper.getUserById(1l)).thenReturn(new User());
User user1 = controller.getUser(1l);
//不建议这种打印自己观察的方式进行单元测试,应采用Assert断言方式,这样能一目了然让我们知道测试是否通过
System.out.println(user1);
}
}

不同于Mockito本身,springboot整合后注入Mock对象采用的是 @SpyBean 和 @MockBean

区别是该注解是为了运行环境在SpringRunner/SpringJUnit4ClassRunner下的。

被@SpyBean 和 @MockBean注解的对象会在初始化时将原对象替换为该Mock对象,从而达到Mock的效果;但依旧存在多层级依赖注入问题,依旧采用反射设置属性的方式解决。

原因猜想

@SpyBean 和 @MockBean仅是替换了原来在Spring上下文的Bean,而这样其实经mock的serviceA中的daoA属性与替换的mock的daoA是没有关联的(相当于有两个对象,mockd的serviceA的daoA属性指向的并不是替换掉的mock(daoA))。

此外本人目前不清楚@SpyBeans和@MockBeans的使用,有了解者还希望能在评论区告知。

结尾

如果大家在使用中遇到过一些问题,欢迎一起讨论。

最后

以上就是着急枫叶为你收集整理的Mockito结合Junit单测使用的全部内容,希望文章能够帮你解决Mockito结合Junit单测使用所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(58)

评论列表共有 0 条评论

立即
投稿
返回
顶部