Springboot 单元测试之Mock

目录

                  1.什么是Mockito?

2. Mockito 简介

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

4.新建实体类,创建接口测试

5. Mock 的限制

6.Mock 使用



1.什么是Mockito?

        Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,避免你为了测试一个方法,却要自行构建整个 bean 的依赖链。

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

                                                                                                    Springboot 单元测试之Mock_第1张图片

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

                                                                                                        Springboot 单元测试之Mock_第2张图片

2. Mockito 简介

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

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

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

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

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

        1.7.1

        
            org.powermock
            powermock-api-mockito2
            ${powermock.version}
            test
        
        
            org.mockito
            mockito-core
            test
        
        
            org.powermock
            powermock-module-junit4
            ${powermock.version}
            test
        

4.新建实体类,创建接口测试

    1. 新建实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {

    private String name;

    private String sex;

    private String age;

}

    2. 创建UserService 

import java.util.Map;

/**
 * @Service 不加无法注入
 */
public class UserService {

    public User saveUser(Map map) {
      return  new User("1","1", "I'm mock 3");
    }

    public int getInt(int i){
        return  i;
    }

    public int getInt(){
        return  1;
    }

}

    3.创建测试类

       使用RunwWith(MockitoJUnitRunner.class)(也可以使用SpringBootRunner.class)来进行mocktio测试,注解@Mock标记一个类或者接口是需要被mock的,在Mock的过程中就相当于@Resource,但是注意一点是Mock是继承or实现了Mock的类,所以Mock出来的方法,全是null或者返回值为null。

@RunWith(MockitoJUnitRunner.class)
public class MockTest {

    @Mock
    private UserService userService;

    @Before
    public void setUp() throws Exception {
        System.out.println("Before");
    }

    @Test
    public void saveProjectBase() {
          
    }


}

5. Mock 的限制

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

6.Mock 使用

   1.我们平常习惯性测试

     这时候我们先不使用 Mockito ,而是真的去调用一个正常的 Spring bean ,测试类写法如下。其实就是很普通的注入 userService bean,然后去调用他的方法。

@RunWith(MockitoJUnitRunner.class)
public class MockTests {


    @Resource
    private UserService userService;

    @Before
    public void setUp() throws Exception {
        System.out.println("Before");
    }

    @Test
    public void saveProjectBase() {
        int i = userService.getInt(1);
        System.out.println(i);
    }

}

   2. mock 的使用

       后面自定返回结果,需要和方法返回结果类型一致

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

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

@Test
 public void saveProjectBase() {

        // 定义当调用mock userService的getUser()方法,并且参数为1时,就返回age为200、name为I'm mock3的user对象
        Mockito.when( userService.getUser(1)).thenReturn(new User("1","1", "I'm mock 3"));
        // 返回的会是名字为I'm mock 3的user对象
        User user = userService.getUser(1);
        System.err.println(user.toString());
        // 返回的会是 null
        User user1 = userService.getUser(2);
        System.err.println(user1.toString());
        
}
    @Test
    public void saveProjectBase() {


        // 定义当调用mock userService的getUser()方法,并且参数为1时,就返回age为200、name为I'm mock3的user对象
        Mockito.when( userService.getUser(1)).thenReturn(new User("1","1", "I'm mock 3"));
        // 返回的会是名字为I'm mock 3的user对象
        User user = userService.getUser(1);
        System.err.println(user.toString());
        // 返回的会是 null
        User user1 = userService.getUser(2);
        // 不能为Null
        Assert.assertNotNull(user1);  // 如若为null  会提示错误 java.lang.AssertionError
        Assert.assertEquals(user.getAge(),  "200");
        Assert.assertEquals(user.getName(), "I'm mock 3");

        // 验证 userService 的 getInt()这个方法是否被调用过
        Mockito.verify(userService).getInt();
    }

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

  此时在执行覆盖率的时候发现都是为零,排查问题发现是之前写的案列还在加载,修改后的单侧没有被加载上。 解决办法只选最外层,但这样的问题就是看覆盖率具体的话,需要一层一层点击进去。

Springboot 单元测试之Mock_第3张图片

 

thenReturn 系列方法

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

Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(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(new User(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(new User()); //会返回100

thenThrow 系列方法

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

Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
User user = userService.getUserById(9); //会抛出一个RuntimeExceptio

如果方法没有返回值的话(即是方法定义为 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));

mock 打桩

        // 对所有注解了@Mock的对象进行模拟
        // MockitoAnnotations.initMocks(this);
        // 不使用注解,可以对单个对象进行 mock
        UserService user = new UserService();
        // 构造被测试对象
        UserService spy = Mockito.spy(user);
        // 打桩,构建当 spy的 getOne 函数执行参数为 1的时候,设置返回的结果 User
        Mockito.when(spy.getOne(1L)).thenReturn(new User("1","1", "I'm mock 3"));
        // 打桩,构建当 spy的 getOne 函数执行参数为 2的时候,设置返回的结果 null
        Mockito.when(spy.getOne(2L)).thenReturn(null);
        // 打桩,构建当 spy的 getOne 函数执行参数为 3的时候,设置结果抛出异常
        Mockito.when(spy.getOne(3L)).thenThrow(new IllegalArgumentException("The id is not support"));
        // 打桩,当 spy.updateUser 执行任何User类型的参数,返回的结果都是true
        Mockito.when(spy.updateUser(Mockito.any(User.class))).thenReturn(true);

mock 验证

        // 没有经过 mock 的 updateUser 方法,它返回的是 false
        //userService = Mockito.spy(UserService.class);  // 经过
        boolean updated = userService.updateUsername("x");
        Assert.assertThat(updated, Matchers.not(true));
        Mockito.verify(userService).updateUsername("x");

        // 验证调用两次getUniqueId
        verify(test, times(2)).updateUsername("x");

        // 也可以使用下面的方法来替代调用的次数
        verify(test, never()).someMethod("never called 从来没有调用");
        verify(test, atLeastOnce()).someMethod("called at least once 至少被调用一次");
        verify(test, atLeast(2)).someMethod("called at least twice 至少被调用5次");
        verify(test, times(5)).someMethod("called five times 被调用5次");
        verify(test, atMost(3)).someMethod("called at most 3 times 至多被调用3次");
        //下面的方法用来检查是否所有的用例都涵盖了,如果没有将测试失败
        //放在所有的测试后面
        verifyNoMoreInteractions(test);

mock其他实列

    // 返回多个值的示例
    @Test
    public void testMoreThanOneReturnValue()  {
        Iterator i= mock(Iterator.class);
        when(i.next()).thenReturn("Mockito").thenReturn("rocks");
        String result= i.next()+" "+i.next();
        //assert
        assertEquals("Mockito rocks", result);
    }

    // 如何根据输入来返回值
    @Test
    public void testReturnValueDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo("Mockito")).thenReturn(1);
        when(c.compareTo("Eclipse")).thenReturn(2);
        //assert
        assertEquals(1, c.compareTo("Mockito"));
    }

    // 返回值独立于输入值
    @Test
    public void testReturnValueInDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(anyInt())).thenReturn(-1);
        //assert
        assertEquals(-1, c.compareTo(9));
    }

    // 根据提供参数的类型返回特定的值
    @Test
    public void testReturnValueInDependentOnMethodParameter2()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(isA(Todo.class))).thenReturn(0);
        //assert
        assertEquals(0, c.compareTo(new Todo(1)));
    }

模拟对象有两种方式:

  1. 对注解了@Mock的对象进行模拟 MockitoAnnotations.initMocks(this);
  2. 对单个对象手动 mock :xxx= Mockito.mock(xxx.class);
/*对void的方法设置模拟*/
Mockito.doAnswer(invocationOnMock -> {
    System.out.println("进入了Mock");
    return null;
}).when(fileRecordDao).insert(Mockito.any());

当 Mockito 监视一个真实的对象的时候,我们也可以模拟这个对象的方法返回我们设置的期望值,

List spy = spy(new LinkedList());  
List spy = spy(new LinkedList());  
// IndexOutOfBoundsException (the list is yet empty)  
when(spy.get(0)).thenReturn("foo");  
// You have to use doReturn() for stubbing  
doReturn("foo").when(spy).get(0); 

 

你可能感兴趣的:(springboot-Mock,java)