Mockito入门使用介绍

689 阅读5分钟

引入依赖

  • SpringBoot项目

spring-boot-starter-test依赖中自动引入了mockito的依赖,所以无需额外引入依赖。

  • 一般项目

单独引入mockito依赖

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>4.5.1</version>
  <scope>test</scope>
</dependency>

使用示例

mock对象的创建与使用

// 静态导入会使代码更简洁
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;


// 使用mock方法创建一个List类型的mock对象
List mockList = mock(List.class);

// 调用mock对象的add方法
boolean res = mockList.add("a");
System.out.println("res = " + res);// res = false

// 调用mock对象的get方法
Object ret = mockList.get(3);
System.out.println("ret = " + ret);// ret = null

// 通过verify校验是否执行过mock对象的指定方法
verify(mockList).add("a");
//verify(mockList).add("b"); //由于未执行过,会抛出异常
verify(mockList).get(3);
  • 既可以mock接口类,也可以mock实现类。

  • mock对象的方法具有默认的返回值,会适当的返回null,原始类型,原始类型的包装类,一个空的集合,例如int/Integer返回0、boolean/Boolean返回false;

  • mock对象会记住所有的交互动作,你可以使用verify来验证是否执行过。

mock方法的执行-打桩stubbing

// 你可以mock具体的类,不仅只是接口
LinkedList mockList = mock(LinkedList.class);

// mock方法的返回值,即打桩stubbing
when(mockList.get(0)).thenReturn("first");
when(mockList.get(1)).thenThrow(new RuntimeException());

// 输出“first”
System.out.println(mockList.get(0));

// 抛出异常
//System.out.println(mockList.get(1));

// 因为get(999) 没有打桩,因此输出null
System.out.println(mockList.get(999));
  • 对mock对象的某个函数调用打桩后,该mock对象的该函数调用将会一直返回固定的值;
  • 打桩动作可以被覆写 : 例如常见的打桩动作可以作为公共方法,然后在不同测试方法中能够重新打桩。
  • 除了thenReturn和thenThrow,还可以调用thenCallRealMethod,thenAnswer等方法。

对无返回值方法打桩

在when方法无法调用无返回值方法,所以对于无返回值方法,需要采用下面的方式进行打桩:

doThrow(new RuntimeException()).when(mockedList).clear();

// 下面的代码会抛出运行时异常
mockedList.clear();

除了doThrow,你还可以使用下面一些方法:

使用内置参数匹配器

Mockito提供了许多内置的参数匹配器,使验证和打桩变得更灵活。

LinkedList mockList = mock(LinkedList.class);

// anyInt()匹配器
when(mockList.get(anyInt())).thenReturn("element");
System.out.println(mockList.get(0));

// any(Class<T> type)匹配器
when(mockList.contains(any(String.class))).thenReturn(true);
System.out.println(mockList.contains(10));
System.out.println(mockList.contains("10"));

常用的内置匹配器有(详细的参数匹配器介绍参考官方文档):

  • any():匹配任何东西,包括空值和可变参数。
  • any(Class type):匹配给定类型的任何对象,不包括空值。
  • anyXxx()
  • contains(String substring):包含给定子字符串的字符串参数。
  • startsWith(String prefix) / endsWith(String suffix):以给定前缀/后缀结束的字符串参数。
  • eq(Xxx value):等值匹配。
  • isA(Class type):对象参数,该参数实现给定的类。
  • isNotNull() / isNull()
  • same(T value):与给定值相同的对象参数。

需要注意的是,如果一个方法参数使用了匹配器,则其他方法参数也必须使用匹配器,例如:

Map mockMap = mock(Map.class);

//when(mockMap.put(anyInt(), "aaa")).thenReturn("ele");// 使用原始类型,会抛出异常
when(mockMap.put(anyInt(), eq("aaa"))).thenReturn("ele");// 使用eq匹配器,正常

System.out.println(mockMap.put(1, "aaa"));

使用自定义参数匹配器

有时候我们需要使用自定义的参数匹配器,一般来说,我们要实现ArgumentMatcher接口,示例如下:

 class ListOfTwoElements implements ArgumentMatcher<List> {
     public boolean matches(List list) {
         return list.size() == 2;
     }
     public String toString() {
         // verify校验失败时打印
         return "[list of 2 elements]";
     }
 }

 List mock = mock(List.class);

 when(mock.addAll(argThat(new ListOfTwoElements))).thenReturn(true);

 mock.addAll(Arrays.asList("one", "two"));

 verify(mock).addAll(argThat(new ListOfTwoElements()));

为了保持它的可读性,可以提取方法,例如:

   verify(mock).addAll(argThat(new ListOfTwoElements()));
   // 变成
   verify(mock).addAll(listOfTwoElements());

也可以使用lambda表达式:

verify(mock).addAll(argThat(list -> list.size() == 2));

此外,还提供了一些原始类型的自定义参数匹配器,如下:

  • booleanThat(Matcher matcher)

  • byteThat(Matcher matcher)

  • charThat(Matcher matcher)

  • doubleThat(Matcher matcher)

  • floatThat(Matcher matcher)

  • intThat(Matcher matcher)

  • longThat(Matcher matcher)

  • shortThat(Matcher matcher)

mock对象的三种方式

一、mock()方法

在前面已经介绍了

二、@mock

  • 在JUnit4中使用

使用测试基类方式:

注意:@Test、@Before、@After都为JUnit4中的

import org.junit.After;
import org.junit.Before;
import org.mockito.MockitoAnnotations;

// 测试基类
public class SampleBaseTestCase {
    private AutoCloseable closeable;

    @Before
    public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @After
    public void releaseMocks() throws Exception {
        closeable.close();
    }
}


import org.junit.Test;
import org.mockito.Mock;
import java.util.ArrayList;
import static org.mockito.Mockito.when;

// 测试类
public class TestDemo2 extends SampleBaseTestCase{
    @Mock
    private ArrayList mockList;

    @Test
    public void test1() {
        when(mockList.get(1)).thenReturn("aaa");
        System.out.println(mockList.get(1));
    }
}

使用注解方式:在测试类上加@RunWith(MockitoJUnitRunner.class)注解

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.ArrayList;
import static org.mockito.Mockito.when;

// 测试类
@RunWith(MockitoJUnitRunner.class)
public class TestDemo2{
    @Mock
    private ArrayList mockList;

    @Test
    public void test1() {
        when(mockList.get(1)).thenReturn("aaa");
        System.out.println(mockList.get(1));
    }
}
  • 在JUnit5中使用

使用测试基类方式:

注意:@Test、@BeforeEach、@AfterEach都为JUnit4中的

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockitoAnnotations;

// 测试基类
public class SampleBaseTestCase {
    private AutoCloseable closeable;

    @BeforeEach
    public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    public void releaseMocks() throws Exception {
        closeable.close();
    }
}


import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import java.util.ArrayList;
import static org.mockito.Mockito.when;

// 测试类
public class TestDemo2 extends SampleBaseTestCase{
    @Mock
    private ArrayList mockList;

    @Test
    public void test1() {
        when(mockList.get(1)).thenReturn("aaa");
        System.out.println(mockList.get(1));
    }
}

使用注解方式:在测试类上加@ExtendWith(MockitoExtension.class)注解

import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import static org.mockito.Mockito.when;

// 测试类
@ExtendWith(MockitoExtension.class)
public class TestDemo2{
    @Mock
    private ArrayList mockList;

    @Test
    public void test1() {
        when(mockList.get(1)).thenReturn("aaa");
        System.out.println(mockList.get(1));
    }
}
  • 配合@InjectMocks注解可以实现依赖注入
@ExtendWith(MockitoExtension.class)
public class InjectTest {
    @Mock
    private UserMapper mockMapper;
    // mockMapper会自动注入到userService中
    @InjectMocks
    private final UserService userService = new UserService();

    @Test
    public void test1() {
        when(mockMapper.count()).thenReturn(99);

        Integer count = userService.countUser();
        System.out.println("count = " + count);// count=99
    }
}

三、@MockBean【Spring环境】

在SpringBoot中,会自动将@MockBean注解生成的Mock对象注入到合适的Bean中

@SpringBootTest
class UserServiceTest {
    @MockBean
    private UserMapper mockMapper;
    // mockMapper会自动注入到userService中
    @Autowired
    private UserService userService;

    @Test
    public void userCountTest() {
        when(mockMapper.count()).thenReturn(99);

        Integer count = userService.countUser();
        System.out.println("count = " + count);
    }
}

spy对象的三种方式

有时候需要在真实对象上mock方法,使用spy可以创建或者放入一个真实对象进行Stubbing,用spy创建的对象都是真实对象。类似于创建mock对象,spy创建对象也有三种方式。

一、spy()方法

List list = new LinkedList();
List spy = spy(list);

二、@spy注解

配合@InjectMocks注解可以实现依赖注入

@Spy
UserMapper userMapper;

@InjectMocks
UserService userService = new UserService();

三、@SpyBean【Spring环境】

@SpyBean
UserMapper userMapper;

编写BDD风格的测试方法

BDD,即行为驱动开发(Behavior-Driven Development)。用行为驱动开发的风格来写测试用例,会使用//given //when //then 作为测试方法的基础组成部分。

在Mockito中,使用提供的 BDDMockito.given(Object) 方法可以很好的应用到BDD风格的测试代码中,测试用例看起来会是这样子:

 import static org.mockito.BDDMockito.*;

 Seller seller = mock(Seller.class);
 Shop shop = new Shop(seller);

 public void shouldBuyBread() throws Exception {
   //given
   given(seller.askForBread()).willReturn(new Bread());

   //when
   Goods goods = shop.buyBread();

   //then
   assertThat(goods, containBread());
 }

参考资料: