• Mockito Spies InjectMocks & 回调测试


    Mockito Spies & InjectMocks

    在今天的学习中,我们将学习如何使用Mockito中的spy、@Spy、@InjectMocks

    Simple Example

    Mockito.spy() 方法允许开发者调用对象的常用方法,同时仍然跟踪每一次调用过程。

    @Test
    public void test0() {
      List<String> mock = mock(LinkedList.class);
      mock.add("one");
      mock.add("two");
    
      List<String> spyList = Mockito.spy(new LinkedList<>());
      spyList.add("one");
      spyList.add("two");
    
      Mockito.verify(spyList).add("one");
      Mockito.verify(spyList).add("two");
      //mock方式 实际数组长度为0
      assertThat(mock).hasSize(0);
      assertThat(spyList).hasSize(2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    开发者可以使用@Spy注解替换 spy方法

    @Spy
    List<String> spyList = new LinkedList<>();
    @Test
    public void test1() {
      spyList.add("one");
      Mockito.verify(spyList).add("two");
      assertThat(spyList).hasSize(2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    启动Mockito注解(例如@Spy、@Mock),开发者需要做相关的配置

    • 在类上使用 @ExtendWith(MockitoExtension.class)

    • 在开始测试之前调用 MockitoAnnotations.openMocks(this); 方法

      @BeforeEach
          void init(){
              MockitoAnnotations.openMocks(this);
          }
      
      • 1
      • 2
      • 3
      • 4

    改变方法返回

    使用spy方式修改方法行为、跟mock的使用方式基本一致。来看看调用size方法如何改变其返回值

    @Test
        public void test2() {
            List<String> list = new LinkedList<>();
            List<String> spyList = Mockito.spy(list);
    
            assertEquals(0, spyList.size());
            //调用size方法 返回100
            Mockito.doReturn(100).when(spyList).size();
            assertThat(spyList).hasSize(100);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    doNothing

    开发者可以使用doNothing()方法更改spy对象指定的方法行为。意思是:当调用方法时 啥都不干

    @Test
        public void test04() {
            List<String> spy = spy(new LinkedList<>());
            //在下标为0处 增加a值 生效
            doNothing().when(spy).add(0, "a");
            spy.add(0, "a");
            //spy.add("hello");
            assertThat(spy).hasSize(0);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    NotAMockException

    学习Mockito时,难免会遇到Mockito NotAMockException异常。在调用mock、spy方法时,该异常为框架内部的比较有代表性的异常。

    在开发时不注意可能会导致此类问题

    @Test
        void test3(){
            List<String> list = new LinkedList<String>();
            Mockito.doReturn(100).when(list).size();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在上面的示例中,并没有对 list对象进行mock。when方法期望得到一个mock、spy对象作为参数

    org.mockito.exceptions.misusing.NotAMockException: 
    Argument passed to when() is not a mock!
    Example of correct stubbing:
        doThrow(new RuntimeException()).when(mock).someMethod();
    
    • 1
    • 2
    • 3
    • 4

    更改后的代码

    @Test
        void test3(){
    //        List list = new LinkedList<>();
    //        Mockito.doReturn(100).when(list).size();
    
            final List<String> spyList = Mockito.spy(new LinkedList<>());
            assertThatNoException().isThrownBy(() -> Mockito.doReturn(100).when(spyList).size());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    InjectMocks

    Mockito 使用以下三种方式,按照指定的顺序注入mock依赖关系:

    1. 基于构造函数的注入-当为类定义了构造函数时,Mockito尝试使用最大的构造函数注入依赖项。

    2. 基于Setter方法-当没有定义构造函数时,Mockito尝试使用Setter方法注入依赖项。

    3. 基于字段-如果没有可能的构造函数或基于字段的注入,那么mockito会尝试将依赖项注入字段本身。

    Simple Example

    import lombok.AllArgsConstructor;
    import lombok.NoArgsConstructor;
    import java.util.Map;
    
    @AllArgsConstructor
    @NoArgsConstructor
    public class MyDictionary {
    
        private Map<String, String> wordMap;
    
        public String getMeaning(final String word) {
            return wordMap.get(word);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    验证测试

    @ExtendWith(MockitoExtension.class)
    public class SpyTest {
    		
        @Mock
        Map<String, String> mockMap;
        //使用 InjectMocks 注解自动向 MyDictionary构造函数注入 mockMap
        @InjectMocks
        MyDictionary dic = new MyDictionary();
        @Test
        public void test05() {
            //dic 里面的map 跟 mockMap是同一个对象
            Mockito.when(mockMap.get("aWord")).thenReturn("aMeaning");
            assertEquals("aMeaning", dic.getMeaning("aWord"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    when VS doXXX

    when(employee.greet()).thenReturn("Hello");
    doReturn("Hello").when(employee).greet();
    
    • 1
    • 2

    上述代码都能实现特定方法的mock功能,并指定返回。但是在一个test unit中并不能同时出现,需注释其中一行代码才能正常运行。那么when、doXXX之间有什么区别?

    基础代码

    为了验证它们之间的差异,先定义一个普通的接口。该接口定义两个方法,其中一个有返回值,一个没有。

    import java.time.DayOfWeek;
    
    public interface Employee {
        String greet();
        void work(DayOfWeek day);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    验证测试

    doxxx模式
    • 测试有返回值的方法 - 以下测试用例可以正常运行

      @ExtendWith(MockitoExtension.class)
      public class EmployeeTest {
          @Mock
          Employee employee;
          @Test
          void test01() {
              // given
              when(employee.greet()).thenReturn("Hello");
              //doReturn("Hello").when(employee).greet();
              // when
              String greeting = employee.greet();
              // then
              assertThat(greeting).isEqualTo("Hello");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • 测试无返回值的方法

      @Test
          void test02() {
              when(employee.work(DayOfWeek.SUNDAY)).thenThrow(new RuntimeException());
              // then
              assertThrows(RuntimeException.class, () -> employee.work(DayOfWeek.SUNDAY));
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      上述代码将不能进行编译,原因是work方法并没有返回值,因此不能对返回对象进行mock。需要将代码改成doxxx的模式才能正常运行,如下:

      @Test
          void test02() {
              //when(employee.work(DayOfWeek.SUNDAY)).thenThrow(new RuntimeException());
              // given
              doThrow(new RuntimeException()).when(employee).work(DayOfWeek.SUNDAY);
              // then
              assertThrows(RuntimeException.class, () -> employee.work(DayOfWeek.SUNDAY));
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    Will 模式

    Mockito框架中的BDDMockito类也提供了另外一种替代语法,只需要将when -> given, do -> will, 其它代码不变

    • 有返回值方法

      given(employee.greet()).willReturn("Hello");
      
      • 1
    • 无返回值方法

      @Test
          void test04() {
              willThrow(new RuntimeException()).given(employee).work(DayOfWeek.SUNDAY);
              assertThrows(RuntimeException.class, () -> employee.work(DayOfWeek.SUNDAY));
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    回调方法测试

    关于java中的回调方法,来看看Mockito框架如何进行测试。

    回调定义: 回调函数作为参数传递给方法的一段代码,该方法预期在给定时间回调(执行参数)。更为经典的是,可能会在稍后的时间发生,有点类似异步回调。

    基础代码

    先按顺序定义以下基础类

    public interface Service {
        void doAction(String request, Callback<String> callback);
    }
    
    • 1
    • 2
    • 3
    public interface Callback<T> {
        String reply(T response);
    }
    
    • 1
    • 2
    • 3
    public class ActionHandler {
        private Service service;
        public ActionHandler(Service service) {
            this.service = service;
        }
        public void doAction() {
            service.doAction("our-request", new Callback<String>() {
                @Override
                public String reply(String response) {
                    return handleResponse(response);
                }
            });
        }
        private String handleResponse(String response) {
            return response + " world";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    验证测试

    public class CallbackTest {
        @Mock
        private Service service;
        //对参数捕获
        @Captor
        private ArgumentCaptor<Callback<String>> callbackCaptor;
        @BeforeEach
        public void setup() {
            MockitoAnnotations.openMocks(this);
        }
        @Test
        public void test01() {
            ActionHandler handler = new ActionHandler(service);
            handler.doAction();
    
            verify(service).doAction(anyString(), callbackCaptor.capture());
    
            Callback<String> callback = callbackCaptor.getValue();
            String response = callback.reply("hello");
    
            assertEquals(
                    "hello world",
                    response, "yes or no");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    maven依赖

    <dependencies>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-webartifactId>
                <version>6.0.2version>
            dependency>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-coreartifactId>
                <version>6.0.2version>
            dependency>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>6.0.2version>
            dependency>
            
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-lang3artifactId>
                <version>3.12.0version>
            dependency>
            
            <dependency>
                <groupId>org.mockitogroupId>
                <artifactId>mockito-junit-jupiterartifactId>
                <version>4.9.0version>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.22version>
                <scope>compilescope>
            dependency>
    
            <dependency>
                <groupId>com.google.guavagroupId>
                <artifactId>guavaartifactId>
                <version>31.1-jreversion>
            dependency>
    
            <dependency>
                <groupId>org.mockitogroupId>
                <artifactId>mockito-coreartifactId>
                <version>4.9.0version>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>org.assertjgroupId>
                <artifactId>assertj-coreartifactId>
                <version>3.23.1version>
                <scope>testscope>
            dependency>
    
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
  • 相关阅读:
    22-09-19 西安 谷粒商城(03)分布式锁、ab测试、LUA脚本、看门狗自动续期
    Docker容器怎么安装Vim编辑器
    产品经理进阶:阻碍产品上市的5个因素
    【项目测试报告】博客系统 + 在线聊天室
    网络技术十二:子网划分
    ESXI之IOChain网络框架
    BUUCTF WEB filejava
    前端用户体验设计:创造卓越的用户界面和交互
    MySQL 索引、事务与存储引擎
    【心电信号】Simulink胎儿心电信号提取【含Matlab源码 1550期】
  • 原文地址:https://blog.csdn.net/u013433591/article/details/128196244