• Java Mockito 笔记


    Mockito


    1 Overview

    2 Maven 项目初始化

    3 示例

            3.1 第一个示例

            3.2 自动 Mock

            3.3 Mock 返回值

            3.4 Mock 参数

            3.5 自动注入 Mock 对象

            3.6 验证调用次数

            3.7 预设 Exception

            3.8 Void Mock

            3.9 级联 Mock

            3.10 部分 Mock

    4 FAQ

            4.1 注意点

    5 References


    1 Overview

    Mockito 是 Java 中用于 Mock 的一个开源项目。

    Mock 用于如下目的

    • 如果依赖的外部系统在测试环境下不可用,使用 Mock 跳过外部系统调用并模拟返回结果
    • 验证是否的确调用了 Mock 对象

    2 Maven 项目初始化

    1. 创建 Maven Quick Start 项目

    2. pom.xml 修改如下

      pom.xml

      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      	<modelVersion>4.0.0</modelVersion>
      
      	<groupId>com.lld</groupId>
      	<artifactId>test.mockito</artifactId>
      	<version>0.0.1-SNAPSHOT</version>
      	<packaging>jar</packaging>
      
      	<name>test.mockito</name>
      	<url>http://maven.apache.org</url>
      	<parent>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-parent</artifactId>
      		<version>1.5.4.RELEASE</version>
      	</parent>
      
      	<properties>
      		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      	</properties>
      
      	<dependencies>
      		<dependency>
      			<groupId>org.springframework.boot</groupId>
      			<artifactId>spring-boot-starter</artifactId>
      		</dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
              </dependency>
      		<dependency>
      			<groupId>org.mockito</groupId>
      			<artifactId>mockito-all</artifactId>
      			<version>1.9.5</version>
      			<scope>test</scope>
      		</dependency>
      	</dependencies>
      	<build>
      		<plugins>
      			<plugin>
      				<groupId>org.springframework.boot</groupId>
      				<artifactId>spring-boot-maven-plugin</artifactId>
      			</plugin>
      		</plugins>
      	</build>
      </project>
      

    3 示例

    3.1 第一个示例

    我们先看如下代码

    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.verify;
    
    import java.util.List;
    
    import org.junit.Test;
    
    public class FirstTest {
    
        @Test
        public void verifyBehavior() {
            @SuppressWarnings("unchecked")
            List<Integer> mock = mock(List.class);
            mock.add(1);
            mock.clear();
            verify(mock).add(1);
            verify(mock).clear();
        }
    }
    

    作为第一个示例,我在此详细解释一下

    首先,我们使用 Mockito.mock() 方来来生成 Mock 对象。这里我们需要注意两点:

    1. 可以直接生成接口的 Mock 对象
    2. 范型对象 Mock

    然后我们调用了 Mock 对象的两个成员方法,在此我们需要更深刻地理解一下 Mock 对象的行为,如果我们在调用 add 方法后打印它的 size,我们会发现结果是 0 而不是 1。也就是说,Mock 对象只是拦截了所有对原始方法的调用并返回对应返回的类型的默认值,而不是真正地实现了这个接口或创建了对象实例。

    然后是两个 verify 方法,表示验证 Mock 对象是否调用了对应的方法。注意验证 add 调用时,可以验证输入参数(本例为 1),如果不想验证,只需要确定是否调用,可以使用如下方式验证

    import static org.mockito.Matchers.anyInt;
    ...
    verify(mock).add(anyInt());

    3.2 自动 Mock

    可以使用如下方式自动生成 Mock 对象

    import org.junit.Before;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    ...
    @Mock
    private List<Integer> mock;
    
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }
    

    3.3 Mock 返回值

    下例 mock一个Iterator类,预设当iterator调用next()时第一次返回hello,以后每次都返回world

    import static org.junit.Assert.assertEquals;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    
    import java.util.Iterator;
    import org.junit.Test;
    
    public class MockReturnTest {
    
        @Test
        public void verifyBehavior1() {
            @SuppressWarnings("unchecked")
            Iterator<String> iterator = mock(Iterator.class);
            when(iterator.next()).thenReturn("hello").thenReturn("world");
            String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
            assertEquals("hello world world", result);
        }
    }
    

    3.4 Mock 参数

    可以使用以下 Mockito 对象来模拟任意输入值

    import static org.mockito.Matchers.any*

    例如 anyString, anyInteger, anyChar 等,也可以使用 any() 方法来生成任意对象,例如

    List<String> mock = mock(List.class);
    mock.add(any(String.class));
    

    或者使用更简单的 Mockito.any(), 如下所示

    import static org.mockito.Mockito.any;
    
    List<String> mock = mock(List.class);
    mock.add(any());
    

    3.5 自动注入 Mock 对象

    对于如下的情况,我们需要 Mock 某对象的内部方法,如下所示,我们需要 Mock MainServer 内部的 OtherService:

    MainService.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MainService {
        @Autowired
        OtherService otherService;
        
        public void call() {
            System.out.println("value is: " + otherService.getValue());
        }
    }
    

    OtherService.java

    import org.springframework.stereotype.Component;
    
    @Component
    public class OtherService {
        public String getValue() {
            return "real value";
        }
    }
    

    常规情况下,我们需要手工注入 Mock 对象,如下所示:

    AutoInjectTest.java

    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = DemoConfig.class)
    public class AutoInjectTest {
        @Autowired
        MainService service;
        
        @Mock
        OtherService otherService;
        
        @Test
        public void manualInjectTest() {
            otherService = mock(OtherService.class);
            when(otherService.getValue()).thenReturn("mock value");
            service.setOtherService(otherService);
            service.call();
        }
    }
    

    PS:需要在 MainService 类中添加 setOtherService() 方法以允许修改 otherService

    其中 DemoConfig 是配置类,对于 Spring Boot 框架,Test 类不会自动注入 Autowired 对象,需要使用 Config 类指定加载类,内容如下:

    DemoConfig.java

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan({ "com.lld.test" })
    public class DemoConfig {
    
    }
    

    但更合理的方式是使用 @InjectMocks 注解来自动注入,如下所示

    AutoInjectTest.java

    import static org.mockito.Mockito.when;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = DemoConfig.class)
    public class AutoInjectTest {
        @Autowired
        @InjectMocks
        MainService service;
        
        @Mock
        OtherService otherService;
        
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
            when(otherService.getValue()).thenReturn("mock value");
        }
        
        @Test
        public void autoInjectTest() {
            service.call();
        }
    }
    

    注意如下几点:

    • 在 @Before 方法中初始化 Mock 对象及自动注入
    • 在需要自动注入成员的类上添加 @InjectMocks 注解

    另外值得注意的是,@InjectMocks 只会注入当前对象的成员,不会递归深度注入对象,例如,我们如果将 MainService 修改如下:

    MainService.java

    @Component
    public class MainService {
        @Autowired
        MiddleService middleService;
    
        public void callMiddle() {
            System.out.println("value is: " + middleService.getValue());
        }
    }
    

    添加 MiddleService 如下所示

    MiddleService.java

    @Component
    public class MiddleService {
        @Autowired
        OtherService otherService;
    
        public String getValue() {
            return otherService.getValue();
        }
    }
    

    这样的话,Unit Test 需要修改如下:

    AutoInjectTest.java

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = DemoConfig.class)
    public class AutoInjectTest {
        @Autowired
        MainService service;
    
        @Mock
        OtherService otherService;
        
        @Autowired
        @InjectMocks
        MiddleService middleService;
    
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
            when(otherService.getValue()).thenReturn("mock value");
        }
    
        @Test
        public void autoInjectDeepTest() {
            service.callMiddle();
        }
    }
    

    3.6 验证调用次数

    如下代码验证了 add 方法需要被调用两次

    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.times;
    import static org.mockito.Mockito.verify;
    import java.util.List;
    import org.junit.Test;
    
    public class CallTimesTest {
        @Test
        public void verifyBehavior1() {
            List<Integer> mock = mock(List.class);
            mock.add(1);
            mock.add(1);
            verify(mock, times(2)).add(1);
        }
    }
    

    3.7 预设 Exception

    下面代码演示了预设 Exceptio 发生

    import static org.mockito.Mockito.doThrow;
    import static org.mockito.Mockito.mock;
    import java.io.IOException;
    import java.io.OutputStream;
    import org.junit.Test;
    
    public class ExceptionTest {
        @Test(expected = IOException.class)
        public void when_thenThrow() throws IOException {
            OutputStream outputStream = mock(OutputStream.class);
            doThrow(new IOException()).when(outputStream).close();
            outputStream.close();
        }
    }
    

    代码说明如下:

    • @Test(expected = IOException.class) 表示该测试需要有 IOException 抛出
    • doThrow 表示指定操作将抛出指定异常

    3.8 Void Mock

    前面的 thenReturn 只适用于有返回值的方法,本例讲述如何 Mock void 方法

    声明服务类如下

    package com.lld.test.mockito;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class VoidService {
        public void sayHi(String name) {
            System.out.println("Hello, " + name);
        }
    }
    

    测试类如下所示

    package com.lld.test.mockito;
    
    import static org.junit.Assert.assertEquals;
    import static org.mockito.Matchers.anyString;
    import static org.mockito.Mockito.doAnswer;
    import static org.mockito.Mockito.doNothing;
    import static org.mockito.Mockito.times;
    import static org.mockito.Mockito.verify;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.ArgumentCaptor;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = DemoConfig.class)
    public class VoidTest {
        @Mock
        VoidService voidService;
    
        @Before
        public void setup() throws Exception {
            MockitoAnnotations.initMocks(this);
        }
        
        @Test
        public void voidTest() {
            doNothing().when(voidService).sayHi(anyString());
            voidService.sayHi("Lindong");
            verify(voidService, times(1)).sayHi(anyString());
        }
        
        @Test
        public void voidArgumentTest() {
            ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
            doNothing().when(voidService).sayHi(argumentCaptor.capture());
            voidService.sayHi("Lindong");
            assertEquals("Lindong", argumentCaptor.getValue());        
        }
    
        @Test
        public void answerTest() {
            doAnswer(answer -> {
                String name = answer.getArgumentAt(0, String.class);
                System.out.println("invoke VoidService with argument: " + name);
                return null;
            }).when(voidService).sayHi(anyString());
            voidService.sayHi("Lindong");
        }
    }
    

    代码说明如下

    • voidTest 演示了如何简单地 Mock void 方法
    • voidArgumentTest 演示了如何获取 void 方法的参数
    • answerTest 演示了如何截获 void 调用

    3.9 级联 Mock

    本例演示了如何自动 Mock 所有对象下的子对象

    import static org.junit.Assert.assertEquals;
    import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    
    import org.junit.Test;
    
    public class DeepMockTest {
        @Test
        public void deepstubsAutoTest() {
            Account account = mock(Account.class, RETURNS_DEEP_STUBS);
            when(account.getRailwayTicket().getDestination()).thenReturn("Beijing");
            account.getRailwayTicket().getDestination();
            verify(account.getRailwayTicket()).getDestination();
            assertEquals("Beijing", account.getRailwayTicket().getDestination());
        }
    
        @Test
        public void deepstubsManualTest() {
            Account account = mock(Account.class);
            RailwayTicket railwayTicket = mock(RailwayTicket.class);
            when(account.getRailwayTicket()).thenReturn(railwayTicket);
            when(railwayTicket.getDestination()).thenReturn("Beijing");
    
            account.getRailwayTicket().getDestination();
            verify(account.getRailwayTicket()).getDestination();
            assertEquals("Beijing", account.getRailwayTicket().getDestination());
        }
    
        public class RailwayTicket {
            private String destination;
    
            public String getDestination() {
                return destination;
            }
    
            public void setDestination(String destination) {
                this.destination = destination;
            }
        }
    
        public class Account {
            private RailwayTicket railwayTicket;
    
            public RailwayTicket getRailwayTicket() {
                return railwayTicket;
            }
    
            public void setRailwayTicket(RailwayTicket railwayTicket) {
                this.railwayTicket = railwayTicket;
            }
        }
    }
    

    代码说明如下:

    • deepstubsAutoTest 演示了自动创建子对象的 Mock (推荐)
    • deepstubsManualTest 演示了手动创建子对象的 Mock

    3.10 部分 Mock

    如下例所示,我们需要使用 Mock 跳过 Exception

    import static org.mockito.Mockito.doNothing;
    import static org.mockito.Mockito.spy;
    
    import org.junit.Test;
    
    public class PartialMockTest {
    
        @Test
        public void partialMockTest() throws Exception {
            TestObj mockObj = spy(new TestObj());
            doNothing().when(mockObj).m1();
            mockObj.m3();
        }
        
        class TestObj {
            public void m1() throws Exception {
                throw new Exception("exception");
            }
            
            public void m2() {
                System.out.println("m2 is invoked");
            }
            
            public void m3() throws Exception {
                m1();
                m2();
            }
        }
    }
    

    我们使用了 spy 方法,它返回的对象是一个真实的对象,所有的方法调用也都是真的方法调用。但像例子中演示的,可以 Mock 掉指定的方法。如果有返回值,也可以和以前的例子一样使用 thenReturn。

    4 FAQ

    4.1 注意点

    1. 对于 @Mock 标注,MockitoAnnotations.initMocks(this); 一定要放在第一行

    2. Mock 对象的所有方法均为假方法,而不是默认实现

    5 References

    Mockito 1.x

    Mockito 2.x

    How to mock with Mockito

  • 相关阅读:
    Java 入门25 继承 this super
    Java 18 String 常用API
    Java 入门21 java 进阶大纲 及复习
    Java 入门20 ArrayList 小案例实践
    面试集合
    面试~设计模式单例模式
    系统入门到实战学习某项技术、有问题找"百度"、学习大佬的技术博客、找开源代码等资料
    面试~双亲委派模型
    面试面向对象7大设计原则
    第一二章学习心得
  • 原文地址:https://www.cnblogs.com/lldwolf/p/8481395.html
Copyright © 2020-2023  润新知