ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot 单元测试利器——Mockito

2021-06-03 19:01:58  阅读:243  来源: 互联网

标签:userDao SpringBoot getUserById Mockito 单元测试 user userService Mock


Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,在了解 Mockito 的具体用法之前,得先了解什么是 Mock 测试。

1. 什么是 Mock 测试?

Mock 测试就是在测试过程中,创建一个假的对象,避免你为了测试一个方法,却要自行构建整个 Bean 的依赖链。

像是以下这张图,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很难测,因为你的返回结果会直接的受外部服务影响,导致你的单元测试可能今天会过、但明天就过不了了。

img

而当我们引入 Mock 测试时,就可以创建一个假的对象,替换掉真实的 Bean B 和 C,这样在调用B、C的方法时,实际上就会去调用这个假的 Mock 对象的方法,而我们就可以自己设定这个 Mock 对象的参数和期望结果,让我们可以专注在测试当前的类 A,而不会受到其他的外部服务影响,这样测试效率就能提高很多。

img

2. Mockito 简介

说完了 Mock 测试的概念,接下来我们进入到今天的主题,Mockito。

Mockito 是一种 Java Mock 框架,他主要就是用来做 Mock 测试的,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 Mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用。

像是 Mockito 可以在单元测试中模拟一个 Service 返回的数据,而不会真正去调用该 Service,这就是上面提到的 Mock 测试精神,也就是通过模拟一个假的 Service 对象,来快速的测试当前我想要测试的类。

目前在 Java 中主流的 Mock 测试工具有 Mockito、JMock、EasyMock等等,而 SpringBoot 目前内建的是 Mockito 框架。

题外话说一下,Mockito 是命名自一种调酒莫吉托(Mojito),外国人也爱玩谐音梗……

3. 在 SpringBoot 单元测试中使用 Mockito

首先在 pom.xml 下新增 spring-boot-starter-test 依赖,该依赖内就有包含了 JUnit、Mockito。

< dependency>

    < groupId> org.springframework.boot </ groupId>

    < artifactId> spring-boot-starter-test </ artifactId>

    < scope> test </ scope>

</ dependency>

先写好一个 UserService,他里面有两个方法 getUserById 和 insertUser,而他们会分别去再去调用 UserDao 这个 bean的 getUserById 和 insertUser 方法。

@Component

public class UserService {

    @Autowired

    private UserDao userDao;

    public User getUserById(Integer id){

    	returnuserDao.getUserById(id);

    }

    publicInteger insertUser(User user){

    	returnuserDao.insertUser(user);

    }

}

User Model 的定义如下:

public class User {

    privateInteger id;

    privateString name;

    //省略 getter/setter

}

如果这时候我们先不使用 Mockito 模拟一个假的 userDao Bean,而是真的去调用一个正常的 Spring Bean 的 userDao 的话,测试类写法如下。其实就是很普通的注入 userService Bean,然后去调用他的方法,而他会再去调用 userDao 取得数据库的数据,然后我们再对返回结果做 Assert 断言检查。

@RunWith(SpringRunner.class)

@SpringBootTest

public class UserServiceTest {

    //先普通的注入一个userService bean

    @Autowired

    private UserService userService;

    @Test

    public void getUserById() throws Exception {

        //普通的使用userService,他里面会再去调用userDao取得数据库的数据

        User user = userService.getUserById( 1);

        //检查结果

        Assert.assertNotNull(user);

        Assert.assertEquals(user.getId, newInteger( 1));

        Assert.assertEquals(user.getName, "John");

    }

}

但是如果 userDao 还没写好,又想先测 userService 的话,就需要使用 Mockito 去模拟一个假的 userDao 出来。

使用方法是在 userDao 上加上一个 @MockBean 注解,当 userDao 被加上这个注解之后,表示 Mockito 会帮我们创建一个假的 Mock 对象,替换掉 Spring 中已存在的那个真实的 userDao Bean,也就是说,注入进 userService 的 userDao Bean,已经被我们替换成假的 Mock 对象了,所以当我们再次调用 userService 的方法时,会去调用的实际上是 mock userDao Bean 的方法,而不是真实的 userDao Bean。

当我们创建了一个假的 userDao 后,我们需要为这个 mock userDao 自定义方法的返回值,这里有一个公式用法,下面这段代码的意思为,当调用了某个 Mock 对象的方法时,就回传我们想要的自定义结果。

Mockito.when( 对象.方法名 ).thenReturn( 自定义结果 )

使用 Mockito 模拟 Bean 的单元测试具体实例如下:

@RunWith(SpringRunner.class)

@SpringBootTest

publicclass UserServiceTest {

    @Autowired

    private UserService userService;

    @MockBean

    private UserDao userDao;

    @Test

    public void getUserById() throws Exception {

    // 定义当调用mock userDao的getUserById方法,并且参数为3时,就返回id为200、name为I'm mock3的user对象

    Mockito.when(userDao.getUserById( 3)).thenReturn( newUser( 200, "I'm mock 3"));

    // 返回的会是名字为I'm mock 3的user对象

    User user = userService.getUserById( 1);

    Assert.assertNotNull(user);

    Assert.assertEquals(user.getId, newInteger( 200));

    Assert.assertEquals(user.getName, "I'm mock 3");

    }

}

Mockito 除了最基本的 Mockito.when( 对象.方法名 ).thenReturn( 自定义结果 ),还提供了其他用法让我们使用。

thenReturn 系列方法

当使用任何整数值调用 userService 的 getUserById 方法时,就回传一个名字为 I'm mock3 的 User 对象。

Mockito.when(userService.getUserById(Mockito.anyInt)).thenReturn( newUser( 3, "I'm mock"));

User user1 = userService.getUserById( 3); // 回传的user的名字为I'm mock

User user2 = userService.getUserById( 200); // 回传的user的名字也为I'm mock

限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象。

Mockito.when(userService.getUserById( 3)).thenReturn( newUser( 3, "I'm mock"));

User user1 = userService.getUserById( 3); // 回传的user的名字为I'm mock

User user2 = userService.getUserById( 200); // 回传的user为null

当调用 userService 的 insertUser 方法时,不管传进来的 user 是什么,都回传 100。

Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn( 100);

Integer i = userService.insertUser( newUser); //会返回100

thenThrow 系列方法

当调用 userService 的 getUserById 时的参数是 9 时,抛出一个 RuntimeException。

Mockito.when(userService.getUserById( 9)).thenThrow( new RuntimeException( "mock throw exception"));

User user = userService.getUserById( 9); //会抛出一个RuntimeException

如果方法没有返回值的话(即是方法定义为 public void myMethod {...}),要改用 doThrow 抛出 Exception。

Mockito.doThrow( new RuntimeException( "mock throw exception")).when(userService).print;

userService.print; //会抛出一个RuntimeException

verify 系列方法

检查调用 userService 的 getUserById、且参数为3的次数是否为1次。

Mockito.verify(userService, Mockito.times( 1)).getUserById(Mockito.eq( 3)) ;

验证调用顺序,验证 userService 是否先调用 getUserById 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用insertUser 方法。

InOrder inOrder = Mockito.inOrder(userService);

inOrder.verify(userService).getUserById( 3);

inOrder.verify(userService).getUserById( 5);

inOrder.verify(userService).insertUser(Mockito.any(User.class));

4. Mockito 的限制

上述就是 Mockito 的 Mock 对象使用方法,不过当使用 Mockito 在 Mock 对象时,有一些限制需要遵守:

  • 不能 Mock 静态方法
  • 不能 Mock private 方法
  • 不能 Mock final class

因此在写代码时,需要做良好的功能拆分,才能够使用 Mockito 的 Mock 技术,帮助我们降低测试时 Bean 的耦合度。

5. 总结

Mockito 是一个非常强大的框架,可以在执行单元测试时帮助我们模拟一个 Bean,提高单元测试的稳定性。

并且大家可以尝试在写代码时,从 Mock 测试的角度来写,更能够写出功能切分良好的代码架构,像是如果有把专门和外部服务沟通的代码抽出来成一个 Bean,在进行单元测试时,只要透过 Mockito 更换掉那个 Bean 就行了。

标签:userDao,SpringBoot,getUserById,Mockito,单元测试,user,userService,Mock
来源: https://www.cnblogs.com/satire/p/14846492.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有