我是靠谱客的博主 着急枫叶,这篇文章主要介绍Mockito结合Junit单测使用,现在分享给大家,希望可以做个参考。

Mockito

文章目录

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

前言

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

简介

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

依赖

复制代码
1
2
3
4
5
6
7
<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.不能模拟静态方法

快速入门

真实代码

复制代码
1
2
3
4
5
6
7
8
@RestController public class UserController { @Resource private UserService userService; public User insertUser(User user){ ... }

测试代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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属性。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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介绍

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//使用自定义答案存根模拟的示例: //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
2
3
4
5
6
7
8
9
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层请求进行测试

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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单测

依赖

复制代码
1
2
3
4
5
6
7
<!-- 该依赖中整合了mockito --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>

快速入门

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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单测使用内容请搜索靠谱客的其他文章。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部