• spring-boog-测试打桩-Mockito


    Mockito用于测试时进行打桩处理;通过它可以指定某个类的某个方法在什么情况下返回什么样的值。

    例如:测试 controller时,依赖 service,这个时候就可以假设当调用 service 某个方法时返回指定的某些值,从而来降低引用类所带来的测试复杂度增加的影响。Mockito就用于这种场景。

    Mockito常用测试场景描述如下:

    • 指定打桩对象的返回值
    • 判断某个打桩对象的某个方法被调用及调用的次数
    • 指定打桩对象抛出某个特定异常

    Mockito的使用,一般有以下几种组合:

    • do/when:包括doThrow(…).when(…)/doReturn(…).when(…)/doAnswer(…).when(…)
    • given/will:包括given(…).willReturn(…)/given(…).willAnswer(…)
    • when/then: 包括when(…).thenReturn(…)/when(…).thenAnswer(…)

    指定打桩对象返回值

    通过Mockito指定打桩对象的返回值时,可以通过以下方式进行:

    given

    given用于对指定方法进行返回值的定制,它需要与will开头的方法一起使用,will开头的方式根据其接收参数的不同,又分成两类:一是接收直接值的,如直接指定返回结果为某个常量;二是接收Answer参数的,可以骑过Answer类的answer方法来根据传入参数定制返回结果。

    Answer对象

    我们实际针对的一般是某个类的某个方法;这个方法可能会有输入参数;考虑这种场景:如果要假设打桩的这个方法,在某个输入时返回值A;在另外一个输入时返回值为B;这种场景就可以通过Answer类来实现。

    given + willAnswer/will

    案例 根据传入的参数,返回不同的数据

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class LearnController2Test {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mvc;
    
        private MockHttpSession session;
    
    
        /**
         * 1. 对于不需要返回的任何值的类的所有方法,可以直接使用MockBean
         * 2. @MockBean 会代理已有的bean的方法,不会执行真实 bean 的具体方法。
         */
        @MockBean
        private LearnService learnService;
    
        @Before
        public void setupMockMvc() {
            //初始化MockMvc对象
            mvc = MockMvcBuilders.webAppContextSetup(wac).build();
    
            //构建session
            session = new MockHttpSession();
            User user = new User("root", "root");
            //拦截器那边会判断用户是否登录,所以这里注入一个用户
            session.setAttribute("user", user);
        }
    
    
        /**
         * 获取教程测试用例
         * <p>
         * get 请求
         * <p>
         * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法
         *
         * @throws Exception
         */
        @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            // 当调用 selectByKey 函数时,返回指定的值
            given(this.learnService.selectByKey(Mockito.any())).willAnswer(new Answer<Object>() {
    
                /**
                 * InvocationOnMock 通过它可以获取打桩方法的实际传入参数清单
                 * @param invocationOnMock
                 * @return
                 * @throws Throwable
                 */
    
                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class);
                    System.out.println("调用方法的实际参数: " + argumentAt);
                    if (argumentAt.equals(Long.parseLong("1001"))) {
                        return learnResource;
                    }
                    return null;
                }
            });
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    
    
    }
    

    given + willReturn

    通过willReturn可以直接指定打桩的方法的返回值
    案例 在任何场景下,都返回指定的数据

    /**
         * 获取教程测试用例
         * <p>
         * get 请求
         * <p>
         * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法
         *
         * @throws Exception
         */
        @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
            
            given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource);
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    
    异常信息:
    java.lang.AssertionError: JSON path "$.author" 
    Expected :嘟嘟MD独立博客
    Actual   :zhang
     <Click to see difference>
    

    when + thenReturn

    thenReturn与willReturn类似

        /**
         * 获取教程测试用例
         * <p>
         * get 请求
         * <p>
         * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法
         *
         * @throws Exception
         */
        @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            when(this.learnService.selectByKey(Mockito.any())).thenReturn(learnResource);
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    

    when + thenAnswer/then

    thenAnswer与willAnswer也类似

        /**
         * 获取教程测试用例
         * <p>
         * get 请求
         * <p>
         * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法
         *
         * @throws Exception
         */
        @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            when(this.learnService.selectByKey(Mockito.any())).thenAnswer(new Answer<Object>() {
                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class);
                    System.out.println("调用方法的实际参数: " + argumentAt);
                    if (argumentAt.equals(Long.parseLong("1001"))) {
                        return learnResource;
                    } else if (argumentAt.equals(Long.parseLong("1002"))) {
                        learnResource.setAuthor("keke");
                        return learnResource;
                    }
                    return null;
                }
            });
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1002")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    
    异常:
    参数为 1001 时
    java.lang.AssertionError: JSON path "$.author" 
    Expected :嘟嘟MD独立博客
    Actual   :zhang
     <Click to see difference>
    
    参数为 1002 时
    java.lang.AssertionError: JSON path "$.author" 
    Expected :嘟嘟MD独立博客
    Actual   :keke
     <Click to see difference>
    
    

    doAnswer/doReturn + when

    // mock 对象不能是 @MockBean 生成的,@MockBean请况下不能用
    @Test
        public void testAnswer1() {
            List<String> mock = Mockito.mock(List.class);
            Mockito.doAnswer(new Answer() {
                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    Object[] args = invocationOnMock.getArguments();
                    System.out.println(args[0]);
                    Integer num = (Integer) args[0];
                    if (num > 3) {
                        return "大于三";
                    } else {
                        return "小于三";
                    }
                }
            }).when(mock).get(Mockito.anyInt());
            // 当 索引为 4 时,期望 大于三
           Assert.assertThat(mock.get(4), equalTo("大于三"));
            // 当 索引为 2 时,期望 小于三
           Assert.assertThat(mock.get(4), equalTo("小于三"));
        }
    
    // mock 对象不能是 @MockBean 生成的,@MockBean请况下不能用
       @Test
        public void testAnswer1() {
            List<String> mock = Mockito.mock(List.class);
            Mockito.doReturn("大于三").when(mock).get(Mockito.anyInt());
            // 当 索引为 2 时
            Assert.assertThat(mock.get(2), equalTo("大于三"));
    
        }
    

    判断某个打桩对象的某个方法被调用及调用的次数

     @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource);
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andDo(MockMvcResultHandlers.print());
    
    
            // 判断 learnService.selectByKey 方法 是否调用了
            Mockito.verify(learnService).selectByKey(1001L);
    
            // 判断 learnService.selectByKey 方法,期望调用 2 次,能过 times 函数指定 selectByKey 函数期望调用几次
            // 也可以通过 Mockito.atLeast 最少几次,Mockito.atMost 是多几次 等函数判断
            Mockito.verify(learnService, Mockito.times(2)).selectByKey(1001L);
    
        }
    
    异常:因为 learnService.selectByKey 方法,调用了1次,而期望调用两次,所以测试出错
    org.mockito.exceptions.verification.TooLittleActualInvocations: 
    learnServiceImpl bean.selectByKey(1001);
    Wanted 2 times:
    -> at com.dudu.outher.LearnController7Test.qryLearn(LearnController7Test.java:86)
    But was 1 time:
    -> at com.dudu.controller.LearnController.qryLearn(LearnController.java:88)
    
    

    指定打桩对象抛出某个特定异常

    given+willThrow

    @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            // 调用 learnService.selectByKey 方法时,抛出异常
            given(this.learnService.selectByKey(Mockito.any())).willThrow(new Exception("查询出错"));
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andDo(MockMvcResultHandlers.print());
    
    
        }
    
    异常:
    org.mockito.exceptions.base.MockitoException: 
    Checked exception is invalid for this method!
    Invalid: java.lang.Exception: 查询出错
    

    when+thenThrow

    @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("zhang");
            learnResource.setAuthor("zhang");
            learnResource.setId(10L);
    
            when(this.learnService.selectByKey(Mockito.any())).thenThrow(new Exception("查询出错"));
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    
    异常:
    org.mockito.exceptions.base.MockitoException: 
    Checked exception is invalid for this method!
    Invalid: java.lang.Exception: 查询出错
    

    doThrow+when
    不能用于 @MockBean 场景下

    @Test
        public void testAnswer1() {
            List<String> mock = Mockito.mock(List.class);
    
            // 调用 mock.size 时,抛出期望的异常信息
            Mockito.doThrow(new Exception("查询出错")).when(mock).size();
    
            // 调用 mock 对象的方法
            mock.size();
    
        }
    
    异常:
    org.mockito.exceptions.base.MockitoException: 
    Checked exception is invalid for this method!
    Invalid: java.lang.Exception: 查询出错
    

    参考

    doThrow:在模拟对象中调用方法时想要抛出异常时使用.
    doReturn:在执行方法时要返回返回值时使用.
    doAnswer:需要对传递给方法的参数执行一些操作
    doNothing:是最简单的列表,基本上它告诉Mockito在调用模拟对象中的方法时什么也不做.有时用于void返回方法或没有副作用的方法,或者与您正在进行的单元测试无关

    https://blog.csdn.net/icarusliu/article/details/78860351

    静态方法测试

    Mockito无法对静态方法进行Mock,如果需要Mock静态方法,需要使用到PowerMockito

    <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-api-mockito</artifactId>
                <version>1.7.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-module-junit4</artifactId>
                <version>1.7.1</version>
            </dependency>
    

    单元测试时,需要使用PowerMockRunner及PrepareForTest两个注解

    @RunWith(PowerMockRunner.class)
    // 对 StringUtils 静态方法进行测试
    @PrepareForTest({StringUtils.class})
    public class TestStatic {
    
      @Test
        public void testStaticMethod() {
            // 对 StringUtils 打桩
            PowerMockito.mockStatic(StringUtils.class);
            PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false);
            boolean bbb = StringUtils.isNoneBlank("bbb");
            System.out.println(bbb);
        }
    }
    

    与SpringBootTest一起使用

    SpringBootTest必须要使用SpringRunner才能生效;但RunWith没有办法指定多个,可以通过PowerMockRunnerDelegate来解决这个问题:

    @RunWith(PowerMockRunner.class)//使用powermock提供的代理来使用
    @PowerMockRunnerDelegate(SpringRunner.class)
    @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})//忽略一些powermock使用的classloader无法处理的类
    @PrepareForTest({StringUtils.class})// @PrepareForTest 可以 mock 多个静态方法
    @SpringBootTest
    public class LearnController11Test {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mvc;
    
        private MockHttpSession session;
    
    
        @MockBean
        private LearnService learnService;
    
        @Before
        public void setupMockMvc() {
            //初始化MockMvc对象
            mvc = MockMvcBuilders.webAppContextSetup(wac).build();
    
            //构建session
            session = new MockHttpSession();
            User user = new User("root", "root");
            //拦截器那边会判断用户是否登录,所以这里注入一个用户
            session.setAttribute("user", user);
        }
    
    
        /**
         * 获取教程测试用例
         * <p>
         * get 请求
         * <p>
         * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法
         *
         * @throws Exception
         */
        @Test
        public void qryLearn() throws Exception {
    
            LearnResource learnResource = new LearnResource();
            learnResource.setUrl("http://www.baidu.com");
            learnResource.setTitle("Spring Boot干货系列");
            learnResource.setAuthor("嘟嘟MD独立博客");
            learnResource.setId(10L);
    
            // 对 service层中的方法进行 mock
            given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource);
    
            // 对 StringUtils 打桩,mock 静态方法
            PowerMockito.mockStatic(StringUtils.class);
            // 当 执行 StringUtils.isNoneBlank 方法时,返回 false
            PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false);
    
            // 实际使用中 StringUtils.isNoneBlank("bbb") 返回 true,但这里返回 false
            boolean result = StringUtils.isNoneBlank("bbb");
            System.out.println("StringUtils.isNoneBlank: " + result);
    
    
            mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                    .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                    .andDo(MockMvcResultHandlers.print());
        }
    
    
    }
    
  • 相关阅读:
    Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境搭建教程
    将Windows Server 2016 打造成工作站(20161030更新)
    阿里云服务器Svn-Server无法连接
    安卓 webview背景色的设置
    安卓handler、thread实现异步任务
    多幅图像下载的时间效率问题
    gridview里item是textView、Button单击事件相应,以及按下效果的取去除
    android dialog圆角显示及解决出现的黑色棱角.(友情提示)
    Android自定义对话框(Dialog)位置,大小
    安卓selector
  • 原文地址:https://www.cnblogs.com/zhangjianbin/p/10083319.html
Copyright © 2020-2023  润新知