当前位置: 代码迷 >> 综合 >> Java单元测试Mock框架-Mockito
  详细解决方案

Java单元测试Mock框架-Mockito

热度:53   发布时间:2024-02-28 08:12:37.0

Mockito

介绍

Mockito 是 Java 单元测试的 Mock 框架,使用 Mock 技术能让单元测试隔离外部依赖,可以避免繁琐的初始化工作,让我们更专注于业务逻辑的测试。

Mockito 的工作原理

通过创建依赖对象的 proxy,所有的调用优先经过 proxy 对象,proxy 对象会拦截所有请求,并返回值我们预置的值进行处理。

Mockito 的包引入

  • gradle

    // https://mvnrepository.com/artifact/org.mockito/mockito-core
    testCompile group: 'org.mockito', name: 'mockito-core', version: '3.5.11'
    
  • maven

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

使用

Mockito 可以通过创建 mock 和 spy 对象,指定返回值来实现模拟的功能。当调用结束时,Mockito 框架提供了验证方法用来检验调用过程的正确性。

1. mock 一个 list

Mockito 提供了静态方法 mock 来模拟创建对象,也可以通过注解 @Mock 创建。

模拟的 List 对象可以调用 List 的方法,verify 验证了调用过程,例如 mock.clear() 没被调用,verify(mock).clear() 则会报错。

  • mock 方法创建模拟对象
@Test
public void testMock() {List mock = Mockito.mock(List.class);mock.add("Hello Mockito");mock.clear();verify(mock).add("Hello Mockito");verify(mock).clear();
}
  • @Mock 注解创建模拟对象
public class MockitoTest {@Mockprivate List mockList;@Beforeprivate void testMockBefore() {MockitoAnnotations.initMocks(this)}@Testpublic void testMock() {mockList.add("Hello Mockito");mockList.clear();verify(mockList).add("Hello Mockito");verify(mockList).clear();}   
}
  • @Mock 注解创建模拟对象
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {@Mockprivate List mockList;@Testpublic void testMock() {mockList.add("Hello Mockito");mockList.clear();verify(mockList).add("Hello Mockito");verify(mockList).clear();}   
}

2. mock 的 list 对象设置返回值

  • when - thenReturn 设置返回值

    List mock = mock(List.class);
    when(mock.size()).thenReturn(1);
    assertEquals(1, mock.size());
    
  • doReturn-when 设置返回值

    List mock = mock(List.class);
    doReturn(1).when(mock).size();
    assertEquals(1, mock.size());
    

3. mock 异常

  • when - thenThrow 模拟异常

    @Test(expected = NullPointerException.class)
    public void testWhenThenThrow() {
          List mock = mock(List.class);when(mock.get(anyInt())).thenThrow(new NullPointerException("NullPointer"));mock.get(0);
    }
    
  • doThrow - when 模拟异常

    @Test(expected = NullPointerException.class)
    public void testDoThrowWhen() throws IOException {
          List mock = mock(List.class);doThrow(new NullPointerException("NullPointer")).when(mock).get(anyInt());mock.get(0);
    }
    

4. mock 设置的连续调用

当设置 mock 的返回值时,如果不连续调用,仅仅是连续设置,则会已最后一次设置为准

@Test
public void testConsecutiveCalls() {List mock = mock(List.class);when(mock.get(0)).thenReturn(0);when(mock.get(0)).thenReturn(1);when(mock.get(0)).thenReturn(2);Assert.assertEquals(2, mock.get(0));
}

连续调用的设置返回值 when().thenReturn().thenReturn()…

@Test(expected = RuntimeException.class)
public void testConsecutiveCalls() {
    List mock = mock(List.class);when(mock.get(0)).thenReturn(100).thenThrow(new RuntimeException());mock.get(0);mock.get(0);
}

第一次调用 mock.get(0) 返回 100,第二次调用 mock.get(0),发生 RuntimeException 异常

5. 验证执行顺序

可以通过 InOrder 验证 mock 的执行顺序

@Test
public void testVerifyOrder() {
    List list1 = mock(List.class);List list2 = mock(List.class);list1.add(1);list2.add("Hello");list1.add(2);list2.add(" Mockito");InOrder inOrder = inOrder(list1, list2);inOrder.verify(list1).add(1);inOrder.verify(list2).add("Hello");inOrder.verify(list1).add(2);inOrder.verify(list2).add(" Mockito");
}

上述例子中,验证 list1, list2 的操作顺序,如果操作变化,则会验证失败。

6. 模拟对象的互动

  • 验证没有任何 Interactions

    @Test
    public void testVerifyInteraction() {
          List list1 = mock(List.class);List list2 = mock(List.class);List list3 = mock(List.class);list1.add(1);// list2, list3 无互动verifyZeroInteractions(list2, list3);
    }
    

    如果 list2,list3 有方法调用,则 verifyZeroInteractions 会失败。

  • 查找冗余的 Interactions

    @Test
    public void testVerifyNoMoreInteraction() {
          List list1 = mock(List.class);list1.add(1);list1.add(2);verify(list1, times(2)).add(anyInt());verifyNoMoreInteractions(list1);List list2 = mock(List.class);list2.add(1);list2.add(2);verify(list2).add(1);verifyNoMoreInteractions(list2);
    }
    

    list1 verify 已经验证两次 add 操作,再次验证无冗余操作则通过;list2 未对 add(2) 进行验证,再次验证无冗余操作则失败。

7. Spy 包装 list 对象

Mock 一个对象并不是真实的对象,但是可以模拟对象行为;Spy 一个对象是真实对象,同时可以模拟对象行为。

@Test
public void testSpyOnRealObjects() {
    List list = new LinkedList();List spy = spy(list);// 真实的 list addspy.add(100);verify(spy).add(100);assertEquals(100, spy.get(0));
}

如果是 mock 的对象,List.get(0) 将返回 null。

8. @InjectMocks 注入对象

  • 注入 mock 对象

    class AddressMap {
          Map<String, String> addressMap;public AddressMap() {
          addressMap = new HashMap<String, String>();}public void add(final String number, final String address) {
          addressMap.put(number, address);}public String get(final String number) {
          return addressMap.get(number);}
    }@Mock
    Map<String, String> addressMap;@InjectMocks
    AddressMap addresses = new AddressMap();@Test
    public void testInjectMocks() {
          when(addressMap.get("001")).thenReturn("Beijing");Assert.assertEquals("Beijing", addresses.get("001"));
    }
    

    mock 对象 addressMap 注入 AddressMap 对象 addresses,测试通过。

  • 注入 spy 对象

    class AddressMap {
          Map<String, String> addressMap;public AddressMap(Map<String, String> map) {
          addressMap = map;}public void add(final String number, final String address) {
          addressMap.put(number, address);}public String get(final String number) {
          return addressMap.get(number);}
    }@Mock
    Map<String, String> addressMap;AddressMap addresses;@Before
    public void testInjectMocksBefore() {
          MockitoAnnotations.initMocks(this);// 初始化addresses = Mockito.spy(new AddressMap(addressMap));    
    }@Test
    public void testInjectMocks() {
          when(addressMap.get("001")).thenReturn("Beijing");Assert.assertEquals("Beijing", addresses.get("001"));
    }
    

    spy 对象注入需要注意初始化,spy 对象是真实对象,没有初始化的对象会抛出异常。

9. ArgumentCaptor 捕获方法参数

class Person {private int id;private String name;Person(int id, String name) {this.id = id;this.name = name;}public int getId() {return id;}public String getName() {return name;}
}
interface PersonDao {public void update(Person person);
}class PersonService {private PersonDao personDao;PersonService(PersonDao dao) {this.personDao = dao;}public void update(int id, String name) {personDao.update(new Person(id, name));}
}
@Test
public void testArgumentCaptor() {PersonDao personDao = mock(PersonDao.class);PersonService service = new PersonService(personDao);ArgumentCaptor<Person> captor = ArgumentCaptor.forClass(Person.class);service.update(1, "jack");verify(personDao).update(captor.capture());Assert.assertEquals(1, captor.getValue().getId());Assert.assertEquals("jack", captor.getValue().getName());
}

ArgumentCaptor 捕获了 update 的参数,用于 mock 方法 getId(),getName() 的参数校验。

10. 重置 mock 对象

List list = mock(List.class);
...
Mockito.reset(xxx);
...

mock 对象重置后恢复初始化状态。

  相关解决方案