SpringBoot Test详解

目录

  • spring-boot-starter-test
    • 1、概述
    • 2、常用注解
      • 2.1、配置类型的注解
      • 2.2、Mock类型的注解
      • 2.3、自动配置类型的注解
      • 2.4、启动测试类型的注解
      • 2.5、相似注解的区别和联系
      • 3、SpringBootTest和Junit的使用
        • 3.1、单元测试
        • 3.2、集成测试
        • 4、MockMvc
          • 4.1、简单示例
          • 4.2、自动配置
          • 4.3、使用方式
            • 1、测试逻辑
            • 2、MockMvcBuilder
            • 3、MockMvcRequestBuilders
            • 4、ResultActions
            • 5、ResultMatchers
            • 6、MvcResult
            • 5、业务代码
            • 6、分层测试
              • 6.1、Dao层测试
              • 6.2、Service层测试
              • 6.3、Controller层测试
              • 7、JSON接口测试

                spring-boot-starter-test

                1、概述

                SpringBoot对单元测试的支持在于提供了一系列注解和工具的集成,它们是通过两个项目提供的:

                • spring-boot-test项目:包含核心功能
                • spring-boot-test-autoconfigure项目:支持自动配置

                  通常情况下,我们通过spring-boot-starter-test的Starter来引入SpringBoot的核心支持项目以及单元测试项目以及单元测试库。

                  spring-boot-starter-test包含的类库如下:

                  • JUnit:一个Java语言的单元测试框架
                  • Spring Test & Spring Boot Test:为SpringBoot应用提供集成测试和工具支持
                  • AssertJ::支持流式断言的Java测试框架
                  • Hamcrest:一个匹配器库
                  • Mockito:一个Java Mock框架
                  • JSONassert:一个针对JSON的断言库
                  • JsonPath:一个JSON XPath库

                    如果SpringBoot提供的基础类无法满足业务需求,我们也可以自行添加依赖。依赖注入的优点之一就是可以轻松使用单元测试。这种方式可以直接通过new来创建对象,而不需要涉及Spring。当然,也可以通过模拟对象来替换真实依赖。

                    如果需要集成测试,比如使用Spring的ApplicationContext,Spring同样能够提供无须部署应用程序或连接到其它基础环境的集成测试。而SpringBoot应用本身就是一个ApplicationContext,因此除了正常使用Spring上下文进行测试,无须执行其它操作。

                    Maven依赖:

                     org.springframework.boot spring-boot-starter-test test

                    2、常用注解

                    从功能上讲,Spring Boot Test中的注解主要分如下几类:

                    类别示例说明
                    配置类型@TestConfiguration等提供一些测试相关的配置入口
                    mock类型@MockBean等提供mock支持
                    启动测试类型@SpringBootTest等以Test结尾的注解,具有加载applicationContext的能力
                    自动配置类型@AutoConfigureJdbc等以AutoConfigure开头的注解,具有加载测试支持功能的能力

                    2.1、配置类型的注解

                    注解作用实践中的使用
                    @TestComponent该注解为另一种@Component,在语义上用来指定某个Bean是专门用于测试的该注解适用与测试代码和正式混合在一起时,不加载被该注解描述的Bean,使用不多
                    @TestConfiguration该注解是另一种@TestComponent,它用于补充额外的Bean或覆盖已存在的Bean在不修改正式代码的前提下,使配置更加灵活
                    @TypeExcludeFilters用来排除@TestConfiguration和@TestComponent适用于测试代码和正式代码混合的场景,使用不多
                    @OverrideAutoConfiguration可用于覆盖@EnableAutoCOnfiguration,与ImportAutoConfiguration结合使用,以限制所加载的自动配置类在不修改正式代码的前提下,提供了修改配置自动配置类的能力
                    @PropertyMapping定义@AutoConfigure注解中用到的变量名称,例如在@AutoConfigureMockMvc中定义名为spring.test.mockmvc.webclient.enabled的变量一般不使用

                    使用@SpringBootApplication启动测试或者生产代码,被@TestComponent描述的Bean会自动被排除掉。如果不是则需要向@SpringBootApplication添加TypeExcludeFilter。

                    2.2、Mock类型的注解

                    注解作用
                    MockBean用于Mock指定的class或被注解的属性
                    MockBeans使@MockBean支持在同一类型或属性上多次出现
                    @SpyBean用于spy指定的class或被注解的属性
                    @SpyBeans使@SpyBeans支持在同一类型或属性上次多次出现

                    @MockBean和@SpyBean这两个注解,在mockito框架中本来已经存在,且功能基本相同。Spring Boot Test又定义一份重复的注解,目的在于使MockBean和SpyBean被ApplicationContext管理,从而方便使用。

                    MockBean和SpyBean功能非常相似,都能模拟方法的各种行为。不同之处在于MockBean是全新的对象,跟正式对象没有关系;而SpyBean与正式对象紧密联系,可以模拟正式对象的部分方法,没有被模拟的方法仍然可以运行正式代码。

                    2.3、自动配置类型的注解

                    注解作用
                    @AutoConfigureJdbc自动配置JDBC
                    @AutoConfigureCache自动配置缓存
                    @AutoConfigureDataLdap自动配置LDAP
                    @AutoConfigureJson自动配置JSON
                    @AutoConfigureJsonTesters自动配置JsonTester
                    @AutoConfigureDataJpa自动配置JPA
                    @AutoConfigureTestEntityManager自动配置TestEntityManager
                    @AutoConfigureRestDocs自动配置Rest Docs
                    @AutoConfigureMockRestServiceServer自动配置MockRestServiceServer
                    @AutoConfigureWebClient自动配置WebClient
                    @AutoConfigureWebFlux自动配置WebFlux
                    @AutoConfigureWebTestClient自动配置WebTestClient
                    @AutoConfigureMockMvc自动配置MockMvc
                    @AutoConfigureWebMvc自动配置WebMvc
                    @AutoConfigureDataNeo4j自动配置Neo4j
                    @AutoConfigureDataRedis自动配置Redis
                    @AutoConfigureJooq自动配置Jooq
                    @AutoCOnfigureTestDatabase自动Test Database,可以使用内存数据库

                    这些注解可以搭配@Test使用,用于开启在@Test中未自动配置的功能。例如@SpringBootTest和@AutoConfigureMockMvc组合后,就可以注入org.springframework.test.web.servlet.MockMvc。

                    自动配置类型有两种使用方式:

                    1. 在功能测试(即使用@SpringBootTest)时显示添加。
                    2. 一般在切片测试中被隐式使用,例如@WebMvcTest注解时,隐式添加了@AutoConfigureCache、@AutoConfigureWebMvc和@AutoConfigureMockMvc。

                    2.4、启动测试类型的注解

                    所有的@*Test注解都被@BootstrapWith注解,它们可以启动ApplicationContext,是测试的入口,所有的测试类必须声明一个@*Test注解。

                    注解作用
                    @SpringBootTest自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,默认web环境为Mock,不见听任务端口
                    @DataRedisTest测试对Redis操作,自动扫描被@RedisHash描述的类,并配置Spring Data Redis的库
                    @DataJpaTest测试基于JPA的数据库操作,同时提供了TestEntityManager替代JPA的EntityManager
                    @DataJdbcTest测试基于Spring Data JDBC的数据库操作
                    @JsonTest测试JSON的序列化和反序列化
                    @WebMvcTest测试Spring MVC中的Controllers
                    @WebFluxTest测试Spring WebFlux中的Controllers
                    @RestClientTest测试对REST客户端的操作
                    @DataLdapTest测试对LDAP的操作
                    @DataMongoTest测试对MongoDB的操作
                    @DataNeo4jTest测试对Neo4j的操作

                    除了@SpringBootTest之外的注解都是用来进行切面测试的,他们会默认导入一些自动配置,点击查看官方文档。

                    一般情况,推荐使用@SpringBootTest而非其它切片测试的注解,简单有效。若某次改动仅涉及特定切片,可以考虑使用切片测试。SpringBootTest是这些注解中最常用的一个,其中包含的配置项如下:

                    • value:指定配置属性
                    • properties:指定配置属性,和value意义相同
                    • classes:指定配置类,等同于@ContextConfiguration中的class,若没有显示指定,将查找嵌套的@Configuration类,然后返回到SpringBootConfiguration搜索配置
                    • webEnviroment:指定web环境,可选值如下:
                      • MOCK:此值为默认值,该类型提供一个mock环境,此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web端口
                      • RANDOM_PORT:启动一个真实的web服务,监听一个随机端口
                      • DEFINED_PORT:启动一个真实的web服务,监听一个定义好的端口(从配置中读取)
                      • NONE:启动一个非web的ApplicationContext,既不提供mock环境,也不提供真实的web服务

                        2.5、相似注解的区别和联系

                        • @TestComment和@Comment:@TestComment是另一种@Component,在语义上用来指定某个Bean是专门用于测试的。使用@SpringBootApplication服务时,@TestComponent会被自动排除
                        • @TestConfiguration和@Configuration:@TestConfiguration是Spring Boot Boot Test提供的,@Configuration是Spring Framework提供的。@TestConfiguration实际上是也是一种@TestComponent,只是这个@TestComponent专门用来做配置用。@TestConfiguration和@Configuration不同,它不会阻止@SpringBootTest的查找机制,相当于是对既有配置的补充或覆盖。
                        • @SpringBootTest和@WebMvcTest(或@*Test):都可以启动Spring的ApplicationContext @SpringBootTest自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,@WebMvcTest不侦测配置,只是默认加载一些自动配置。@SpringBootTest测试范围一般比@WebMvcTest大。
                        • @MockBean和@SpyBean:都能模拟方法的各种行为。不同之处在于MockBean是全新的对象,跟正式对象没有关系;而SpyBean与正式对象紧密联系,可以模拟正式对象的部分方法,没有被模拟的方法仍然可以运行正式代码。

                          3、SpringBootTest和Junit的使用

                          整体上,Spring Boot Test支持的测试种类,大致可以分为如下三类:

                          1. 单元测试:一般面向方法,编写一般业务代码时,测试成本较大。涉及到的注解有@Test。
                          2. 切片测试:一般面向于测试的边界功能,介于单元测试和功能测试之间。涉及到的注解有@WebMvcTest等。主要就是对于Controller的测试,分离了Service层,这里就涉及到Mock控制层所依赖的组件了。
                          3. 功能测试:一般面向某个完整的业务功能,同时也可以使用切面测试中mock能力,推荐使用。涉及到的注解有@SpringBootTest等。

                          3.1、单元测试

                          默认无参数的@SpringBootTest 注解会加载一个Web Application Context并提供Mock Web Environment,但是不会启动内置的server。这点从日志中没有打印Tomcat started on port(s)可以佐证。

                          @SpringBootTest
                          public class AppTest { @Autowired
                              UserMapper userMapper;
                              @Test
                              public void test() { User user = new User();
                                  user.setName("tom");
                                  user.setAge(18);
                                  user.setHeight(1.88);
                                  Assertions.assertThat(userMapper.add(user)).isEqualTo(1);
                              }
                          }
                          

                          3.2、集成测试

                          //指定@SpringBootTest的Web Environment为RANDOM_PORT
                          //此时,将会加载ApplicationContext,并启动Server,Server监听在随机端口上。
                          //在测试类中通过@LocalServerPort获取该端口值
                          @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
                          public class DemoTest { @LocalServerPort
                              private Integer port;
                              @Test
                              @DisplayName("should access application")
                              public void shouldAccessApplication() { Assertions.assertThat(port).isGreaterThan(1024);
                              }
                          }
                          

                          也可以通过指定@SpringBootTest的Web Environment为DEFINED_PORT 来指定server侦听应用程序配置的端口,默认为8080。不过这种指定端口的方式很少使用,因为如果本地同时启动应用时,会导致端口冲突。

                          4、MockMvc

                          MockMvc可以做到不启动项目工程就可以对结构进行测试。MockMvc实现了对HTTP请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,同时提供了一套验证的工具,使得请求的验证同一而且方便。

                          4.1、简单示例

                          创建一个简单的TestController,提供一个方法,返回一个字符串:

                          @RestController
                          public class TestController { @RequestMapping("/mock")
                              public String mock(String name) { return "Hello " + name + "!";
                              }
                          }
                          

                          单元测试:

                          @SpringBootTest
                          @AutoConfigureMockMvc
                          class TestControllerTest { @Autowired
                              private MockMvc mockMvc;
                              @Test
                              void mock() throws Exception { //mockMvc.perform执行一个请求
                                  mockMvc.perform(MockMvcRequestBuilders
                                      //构造请求
                                      .get("/mock")
                                      //设置返回值类型
                                      .accept(MediaType.APPLICATION_JSON)
                                      //添加请求参数
                                      .param("name", "tom"))
                                  //添加执行完成后的断言
                                  .andExpect(MockMvcResultMatchers.status().isOk())
                                  .andExpect(MockMvcResultMatchers.content().string("Hello tom!"))
                                  //添加一个结果处理器,此处打印整个响应结果信息
                                  .andDo(MockMvcResultHandlers.print());
                              }
                          }
                          

                          运行测试输出:

                          MockHttpServletRequest:
                                HTTP Method = GET
                                Request URI = /mock
                                 Parameters = {name=[tom]}
                                    Headers = [Accept:"application/json"]
                                       Body = null
                              Session Attrs = {}
                          Handler:
                                       Type = pers.zhang.controller.TestController
                                     Method = pers.zhang.controller.TestController#mock(String)
                          Async:
                              Async started = false
                               Async result = null
                          Resolved Exception:
                                       Type = null
                          ModelAndView:
                                  View name = null
                                       View = null
                                      Model = null
                          FlashMap:
                                 Attributes = null
                          MockHttpServletResponse:
                                     Status = 200
                              Error message = null
                                    Headers = [Content-Type:"application/json", Content-Length:"10"]
                               Content type = application/json
                                       Body = Hello tom!
                              Forwarded URL = null
                             Redirected URL = null
                                    Cookies = []
                          

                          @AutoConfigureMockMvc注解提供了自动配置MockMvc的功能。@Autowired注入MockMvc对象。

                          MockMvc对象可以通过接口M哦查看Mv吃Builder的实现类获得。该接口提供一个唯一的build方法来构造MockMvc。主要有两个实现类:

                          • StandaloneMockMvcBuilder:独立安装
                          • DefaultMockMvcBuilder:集成Web环境测试(并不会真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)

                            MockMvcBuilders提供了对应的standaloneSetup和webAppContextSetup两种创建方法,在使用时直接调用即可,默认使用DefaultMOckMvcBuilder。

                            整个单元测试包含一下步骤:

                            1. 准备测试环境
                            2. 执行MockMvc请求
                            3. 添加验证断言
                            4. 添加结果处理器
                            5. 得到MvcResult进行自定义断言/进行下一步的异步请求
                            6. 卸载测试环境

                            4.2、自动配置

                            @AutoConfigureMockMvc提供了自动配置MockMvc的功能,源码如下:

                            @Target({ ElementType.TYPE, ElementType.METHOD })
                            @Retention(RetentionPolicy.RUNTIME)
                            @Documented
                            @Inherited
                            @ImportAutoConfiguration
                            @PropertyMapping("spring.test.mockmvc")
                            public @interface AutoConfigureMockMvc {//是否应向MockMvc注册来自应用程序上下文的filter,默认true
                            	boolean addFilters() default true;
                            	//每次MockMvc调用后应如何打印MvcResult信息
                            	@PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)
                            	MockMvcPrint print() default MockMvcPrint.DEFAULT;
                            	//如果MvcResult仅在测试失败时才打印信息。默认true,则表示只在失败时打印
                            	boolean printOnlyOnFailure() default true;
                            	//当HtmlUnit在类路径上时,是否应该自动配置WebClient。默认为true
                            	@PropertyMapping("webclient.enabled")
                            	boolean webClientEnabled() default true;
                            	//当Selenium位于类路径上时,是否应自动配置WebDriver。默认为true
                            	@PropertyMapping("webdriver.enabled")
                            	boolean webDriverEnabled() default true;
                            }
                            

                            在AutoConfigureMockMvc的源码中,我们重点看它组合的@ImportAutoConfiguration注解。该注解同样是SpringBoot自动配置项目提供的,其功能类似@EnableAutoConfiguration,但又略有区别。@ImportAutoConfiguration同样用于导入自动配置类,不仅可以像@EnableAutoConfiguration那样排除指定的自动配置配置类,还可以指定使用哪些自动配置类,这是它们之间的重要区别之一。

                            另外,@ImportAutoConfiguration使用的排序规则与@EnableAutoConfiguration的相同,通常情况下,建议优先使用@EnableAutoConfiguration注解进行自动配置。但在单元测试中,则可考虑优先使用@ImportAutoCOnfiguration。源码如下:

                            @Target(ElementType.TYPE)
                            @Retention(RetentionPolicy.RUNTIME)
                            @Documented
                            @Inherited
                            @Import(ImportAutoConfigurationImportSelector.class)
                            public @interface ImportAutoConfiguration {//指定引入的自动配置类
                            	@AliasFor("classes")
                            	Class[] value() default {};
                            	//指定引入的自动配置类。如果为空,则使用META-INF/spring.factories中注册的指定类
                            	//其中spring.factories中注册的key为被该注解的类的全限定名称
                            	@AliasFor("value")
                            	Class[] classes() default {};
                            	//排除指定自动配置类
                            	Class[] exclude() default {};
                            }
                            

                            通过value属性,提供了指定自动配置类的功能,可以通过细粒度控制,根据需要引入相应功能的自动配置。没有@EnableAutoConfiguration一次注入全局生效的特性,但是有了指定的灵活性。

                            更值得注意的是classes属性,它也是用来指定自动配置类的,但它的特殊之处在于,如果未进行指定,则会默认搜索项目META-INF/spring.factories文件中注册的类,但是它

                            搜索的注册类在spring.factories中的key是被@ImportAutoConfiguration注解的类的全限

                            定名称。显然,这里的key为org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc。以上功能也就解释了为什么在单元测试中更多的是使用@ImportAutoConfiguration注解来进行自动配置了。

                            在spring-boot-test-autoconfigure项目的spring.factories文件中的相关配置如下:

                            # AutoConfigureMockMvc auto-configuration imports
                            org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\
                            org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\
                            org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration,\
                            org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration,\
                            org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
                            org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
                            org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
                            org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
                            org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
                            org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration
                            

                            也就是说,当使用@ImportAutoConfiguration注解,并未指定classes属性值时,默认自动配置上述自动配置类。

                            使用@AutoConfigureMockMvc注解会导入MockMvcAutoConfiguration自动配置类,该类就是专门为MockMvc相关功能提供自动配置的。

                            @Configuration(proxyBeanMethods = false)
                            @ConditionalOnWebApplication(type = Type.SERVLET)
                            @AutoConfigureAfter(WebMvcAutoConfiguration.class)
                            @EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
                            public class MockMvcAutoConfiguration {private final WebApplicationContext context;
                            	private final WebMvcProperties webMvcProperties;
                            	MockMvcAutoConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) {this.context = context;
                            		this.webMvcProperties = webMvcProperties;
                            	}
                            	....
                            }
                            

                            注解部分说明,MockMvcAutoConfiguration需要在Web应用程序类型为Servlet,且在WebMvcAutoConfiguration自动配置之后进行自动配置。

                            另外,通过@EnableConfigurationProperties导入了ServerProperties和WebMvcProperties两个配置属性类,并通过构造方法设置为成员变量。

                            4.3、使用方式

                            1、测试逻辑
                            1. MockMvcBuilder构造MockMvc的构造器
                            2. mockMvc调用perform,执行一个RequestBuilder请求,调用Controller的业务处理逻辑
                            3. perform返回ResultActions,返回操作结果,通过ResultActions,提供了统一的验证方式
                            4. 使用StatusResultMatchers对请求结果进行验证
                            5. 使用ContentResultMatchers对请求返回的内容进行验证
                            2、MockMvcBuilder

                            MockMvc是spring测试下的一个非常好用的类,他们的初始化需要在setUp中进行。

                            MockMvcBuilder是用来构造MockMvc的构造器,其主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,前者继承了后者。

                            1. MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc
                            2. MockMvcBuilders.standaloneSetup(Object... controllers):通过参数指定一组控制器,这样就不需要从上下文获取了,比如this.mockMvc = MockMvcBuilders.standaloneSetup(this.controller).build();这些Builder还提供了其他api,可以自行百度
                            3、MockMvcRequestBuilders

                            从名字可以看出,RequestBuilder用来构建请求的,其提供了一个方法buildRequest(ServletContext servletContext)用于构建MockHttpServletRequest;其主要有两个子类MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如文件上传使用),即用来Mock客户端请求需要的所有数据。

                            常用API:

                            • MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的RequestBuilder,如果在controller的方法中method选择的是RequestMethod.GET,那在controllerTest中对应就要使用MockMvcRequestBuilders.get
                            • post(String urlTemplate, Object... urlVariables):同get类似,但是是POST方法
                            • put(String urlTemplate, Object... urlVariables):同get类似,但是是PUT方法
                            • delete(String urlTemplate, Object... urlVariables) :同get类似,但是是DELETE方法
                            • options(String urlTemplate, Object... urlVariables):同get类似,但是是OPTIONS方法
                              4、ResultActions

                              调用MockMvc.perform(RequestBuilder requestBuilder)后将得到ResultActions,对ResultActions有以下三种处理:

                              • ResultActions.andExpect:添加执行完成后的断言。添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确
                              • ResultActions.andDo:添加一个结果处理器,比如此处使用.andDo(MockMvcResultHandlers.print())输出整个响应结果信息,可以在调试的时候使用
                              • ResultActions.andReturn:表示执行完成后返回相应的结果

                                ResultHandler用于对处理的结果进行相应处理的,比如输出整个请求/响应等信息方便调试,Spring mvc测试框架提供了MockMvcResultHandlers静态工厂方法,该工厂提供了ResultHandler print()返回一个输出MvcResult详细信息到控制台的ResultHandler实现。

                                使用Content-type来指定不同格式的请求信息:

                                ALL = new MediaType("*", "*");
                                APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
                                APPLICATION_CBOR = new MediaType("application", "cbor");
                                APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
                                APPLICATION_JSON = new MediaType("application", "json");
                                APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
                                APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
                                APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
                                APPLICATION_PDF = new MediaType("application", "pdf");
                                APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
                                APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json", StandardCharsets.UTF_8);
                                APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
                                APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
                                APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
                                APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
                                APPLICATION_XML = new MediaType("application", "xml");
                                IMAGE_GIF = new MediaType("image", "gif");
                                IMAGE_JPEG = new MediaType("image", "jpeg");
                                IMAGE_PNG = new MediaType("image", "png");
                                MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
                                MULTIPART_MIXED = new MediaType("multipart", "mixed");
                                MULTIPART_RELATED = new MediaType("multipart", "related");
                                TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
                                TEXT_HTML = new MediaType("text", "html");
                                TEXT_MARKDOWN = new MediaType("text", "markdown");
                                TEXT_PLAIN = new MediaType("text", "plain");
                                TEXT_XML = new MediaType("text", "xml");
                                
                                5、ResultMatchers

                                ResultMatcher用来匹配执行完请求后的结果验证,其就一个match(MvcResult result)断言方法,如果匹配失败将抛出相应的异常,spring mvc测试框架提供了很多***ResultMatchers来满足测试需求。

                                MockMvcResultMatchers类提供了许多静态方法,提供了多种匹配器:

                                • request():返回RequestResultMatchers,访问与请求相关的断言
                                  • asyncStarted:断言异步处理开始
                                  • asyncNotStarted:断言异步不开始
                                  • asyncResult:断言使用给定匹配器进行异步处理的结果
                                  • attribute:用于断言请求属性值
                                  • sessionAttribute:用于断言Session会话属性值
                                  • sessionAttributeDoesNotExist:断言Session会话属性不存在
                                  • handler():返回HandlerResultMatchers,对处理请求的处理程序的断言的访问
                                    • handlerType:断言处理请求的处理程序的类型
                                    • methodCall:断言用于处理请求的控制器方法
                                    • methodName:断言用于处理请求的控制器方法的名称
                                    • method:断言用于处理请求的控制器方法
                                    • model():ModelResultMatchers,访问与模型相关的断言
                                      • attribute:断言一个模型属性值
                                      • attributeExists:断言一个模型属性存在
                                      • attributeDoesNotExist:断言一个模型属性不存在
                                      • attributeErrorCount:断言给定的模型属性有指定个数的错误
                                      • attributeHasErrors:断言给定的模型属性有错误
                                      • attributeHasNoErrors:断言给定的模型属性没有错误
                                      • attributeHasFieldErrors:断言给定的模型属性字段有错误
                                      • attributeHasFieldErrorCode:使用精确字符串匹配断言模型属性的字段错误代码
                                      • errorCount:断言模型中的错误总数
                                      • hasErrors:断言模型中有错误
                                      • hasNoErrors:断言模型中没有错误
                                      • size:断言模型属性的数量
                                      • view():返回ViewResultMatchers,访问所选视图上的断言
                                        • name:断言视图名
                                        • flash():返回FlashAttributeResultMatchers,访问flash属性断言
                                          • attribute:断言flash属性的值
                                          • attributeExists:断言给定的flash属性是否存在
                                          • attributeCount:断言flash属性的数量
                                          • forwardedUrl(@Nullable String expectedUrl):断言请求被转发到给定的URL
                                          • forwardedUrlTemplate(String urlTemplate, Object… uriVars):断言请求被转发到给定的URL模板
                                          • forwardedUrlPattern(String urlPattern):断言请求被转发到给定的URL
                                          • redirectedUrl(String expectedUrl):断言请求被重定向到给定的URL
                                          • redirectedUrlTemplate(String urlTemplate, Object… uriVars):断言请求被重定向到给定的URL模板
                                          • redirectedUrlPattern(String urlPattern):断言请求被重定向到给定的URL
                                          • status():返回StatusResultMatchers,访问响应状态断言
                                            • is:断言响应状态码
                                            • is1xxInformational:断言响应状态码在1xx范围内
                                            • is2xxSuccessful:断言响应状态码在2xx范围内
                                            • is3xxRedirection:断言响应状态码在3xx范围内
                                            • is4xxClientError:断言响应状态码在4xx范围内
                                            • is5xxServerError:断言响应状态码在5xx范围内
                                            • reason:断言Servlet响应错误消息
                                            • isContinue:响应状态码是100
                                            • isSwitchingProtocols:响应状态码是101
                                            • isProcessing:响应状态码是102
                                            • isCheckpoint:响应状态码是103
                                            • isOk:响应状态码是200
                                            • isCreated:响应状态码是201
                                            • isAccepted:响应状态码是202
                                            • isNonAuthoritativeInformation:响应状态码是203
                                            • isNoContent:响应状态码是204
                                            • isResetContent:响应状态码是205
                                            • isPartialContent:响应状态码是206
                                            • isMultiStatus:响应状态码是207
                                            • isAlreadyReported:响应状态码是208
                                            • isImUsed:响应状态码是226
                                            • isMultipleChoices:响应状态码是300
                                            • isMovedPermanently:响应状态码是301
                                            • isFound:响应状态码是302
                                            • isSeeOther:响应状态码是303
                                            • isNotModified:响应状态码是304
                                            • isUseProxy:响应状态码是305
                                            • isTemporaryRedirect:响应状态码是307
                                            • isPermanentRedirect:响应状态码是308
                                            • isBadRequest:响应状态码是400
                                            • isUnauthorized:响应状态码是401
                                            • isPaymentRequired:响应状态码是402
                                            • isForbidden:响应状态码是403
                                            • isNotFound:响应状态码是404
                                            • isMethodNotAllowed:响应状态码是405
                                            • isNotAcceptable:响应状态码是406
                                            • isProxyAuthenticationRequired:响应状态码是407
                                            • isRequestTimeout:响应状态码是408
                                            • isConflict:响应状态码是409
                                            • isGone:响应状态码是410
                                            • isLengthRequired:响应状态码是411
                                            • isPreconditionFailed:响应状态码是412
                                            • isPayloadTooLarge:响应状态码是413
                                            • isUriTooLong:响应状态码是414
                                            • isUnsupportedMediaType:响应状态码是415
                                            • isRequestedRangeNotSatisfiable:响应状态码是416
                                            • isExpectationFailed:响应状态码是417
                                            • isIAmATeapot:响应状态码是418
                                            • isInsufficientSpaceOnResource:响应状态码是419
                                            • isMethodFailure:响应状态码是420
                                            • isDestinationLocked:响应状态码是421
                                            • isUnprocessableEntity:响应状态码是422
                                            • isLocked:响应状态码是423
                                            • isFailedDependency:响应状态码是424
                                            • isTooEarly:响应状态码是425
                                            • isUpgradeRequired:响应状态码是426
                                            • isPreconditionRequired:响应状态码是428
                                            • isTooManyRequests:响应状态码是429
                                            • isRequestHeaderFieldsTooLarge:响应状态码是431
                                            • isUnavailableForLegalReasons:响应状态码是451
                                            • isInternalServerError:响应状态码是500
                                            • isNotImplemented:响应状态码是501
                                            • isBadGateway:响应状态码是502
                                            • isServiceUnavailable:响应状态码是503
                                            • isGatewayTimeout:响应状态码是504
                                            • isHttpVersionNotSupported:响应状态码是505
                                            • isVariantAlsoNegotiates:响应状态码是506
                                            • isInsufficientStorage:响应状态码是507
                                            • isLoopDetected:响应状态码是508
                                            • isBandwidthLimitExceeded:响应状态码是509
                                            • isNotExtended:响应状态码是510
                                            • isNetworkAuthenticationRequired:响应状态码是511
                                            • header():返回HeaderResultMatchers,访问响应头断言
                                              • string:断言响应头的主值
                                              • stringValues:断言响应头的值
                                              • exists:断言指定的响应头存在
                                              • doesNotExist:断言指定的响应头不存在
                                              • longValue:将指定响应头断言为long
                                              • dateValue:断言指定响应头解析为日期
                                              • content():返回ContentResultMatchers,访问响应体断言
                                                • contentType:断言Content-Type,给定的内容类型必须完全匹配,包括类型、子类型和参数
                                                • contentTypeCompatibleWith:断言Content-Type与指定的类型兼容
                                                • encoding:断言响应的字符编码
                                                • string:断言响应体内容(作为字符串)
                                                • bytes:断言响应体内容(作为字节数组)
                                                • xml:断言响应体内容(作为Xml)
                                                • source:断言响应体内容(作为Source)
                                                • json:断言响应体内容(作为json)
                                                • jsonPath(String expression, Object… args):返回JsonPathResultMatchers,使用JsonPath表达式访问响应体断言
                                                  • prefix:断言JSON有效负载是否添加了给定的前缀
                                                  • value:根据JsonPath断言结果值
                                                  • exists:根据JsonPath断言在给定路径上存在非空值
                                                  • doesNotExist:根据JsonPath断言在给定路径上不存在非空值
                                                  • isEmpty:根据JsonPath断言给定路径中存在空值
                                                  • isNotEmpty:根据JsonPath断言给定路径中不存在空值
                                                  • hasJsonPath:根据JsonPath断言给定路径中存在一个值
                                                  • doesNotHaveJsonPath:根据JsonPath断言给定路径中不存在一个值
                                                  • isString:根据JsonPath断言结果是String
                                                  • isBoolean:根据JsonPath断言结果是Boolean
                                                  • isNumber:根据JsonPath断言结果是Number
                                                  • isArray:根据JsonPath断言结果是Array
                                                  • isMap:根据JsonPath断言结果是Map
                                                  • jsonPath(String expression, Matcher matcher):根据响应体计算给定的JsonPath表达式,并使用给定的Hamcrest Matcher断言结果值
                                                  • jsonPath(String expression, Matcher matcher, Class targetType):根据响应体计算给定的JsonPath表达式,并使用给定的Hamcrest Matcher断言结果值,在应用匹配器之前将结果值强制转换为给定的目标类型
                                                  • xpath(String expression, Object… args):返回XpathResultMatchers,使用XPath表达式访问响应体断言,以检查响应体的特定子集
                                                    • node:计算XPath并断言使用给定的Hamcrest Matcher找到的Node内容
                                                    • nodeList:计算XPath并断言与给定的Hamcrest Matcher找到的NodeList内容
                                                    • exists:计算XPath并断言内容存在
                                                    • doesNotExist:计算XPath并断言内容不存在
                                                    • nodeCount:计算XPath并断言使用给定的Hamcrest Matcher找到的节点数
                                                    • string:应用XPath并断言用给定的Hamcrest Matcher找到的String值
                                                    • number:计算XPath并断言用给定的Hamcrest Matcher找到的Double值
                                                    • booleanValue:计算XPath并断言找到的Boolean
                                                    • xpath(String expression, Map namespaces, Object… args):使用XPath表达式访问响应体断言,以检查响应体的特定子集
                                                    • cookie():返回CookieResultMatchers,访问响应cookie断言
                                                      • value:使用给定的Hamcrest Matcher断言一个cookie值
                                                      • exists:断言cookie存在
                                                      • doesNotExist:断言cookie不存在
                                                      • maxAge:使用Hamcrest Matcher断言cookie的maxAge
                                                      • path:用Hamcrest Matcher断言一个cookie的路径
                                                      • domain:使用Hamcrest Matcher断言cookie的域
                                                      • comment:用Hamcrest Matcher断言一个cookie的注释
                                                      • version:用Hamcrest Matcher断言一个cookie的版本
                                                      • secure:断言cookie是否必须通过安全协议发送
                                                      • httpOnly:断言cookie是否只能是HTTP
                                                        6、MvcResult

                                                        即执行完控制器后得到的整个结果,并不仅仅是返回值,其包含了测试时需要的所有信息。

                                                        MvcResult有两个实现类:

                                                        • DefaultMvcResult:一个简单的默认实现
                                                        • PrintingMvcResult:待打印功能的实现

                                                          常用方法:

                                                          • getRequest:返回执行的请求
                                                          • getResponse:返回结果响应
                                                          • getHandler:返回已执行的处理程序
                                                          • getInterceptors:返回处理程序周围的拦截器
                                                          • getModelAndView:返回处理程序准备的ModelAndView
                                                          • getResolvedException:返回由处理程序引发并通过HandlerExceptionResolver成功解决的任何异常
                                                          • getFlashMap:返回在请求处理期间保存的FlashMap
                                                          • getAsyncResult:得到异步执行的结果

                                                            5、业务代码

                                                            实体类:

                                                            @Data
                                                            @NoArgsConstructor
                                                            @AllArgsConstructor
                                                            public class User { private Long id;
                                                                private String name;
                                                                private Integer age;
                                                                private Double height;
                                                            }
                                                            

                                                            Dao层:

                                                            @Mapper
                                                            public interface UserMapper { List list();
                                                                Integer add(User user);
                                                                Integer update(User user);
                                                                Integer deleteById(Long id);
                                                                User getById(Long id);
                                                            }
                                                            

                                                            UserMapper.xml:

                                                              INSERT INTO user (name, age, height)
                                                                    VALUES (#{name}, #{age}, #{height})   UPDATE user SET name = #{name}, age = #{age}, height = #{height}
                                                                    WHERE id = #{id}   DELETE FROM user
                                                                    WHERE id = #{id}   

                                                            Service层:

                                                            public interface UserService { List list();
                                                                Integer add(User user);
                                                                Integer update(User user);
                                                                Integer deleteById(Long id);
                                                                User getById(Long id);
                                                            }
                                                            
                                                            @Service
                                                            public class UserServiceImpl implements UserService { @Autowired
                                                                private UserMapper userMapper;
                                                                @Override
                                                                public List list() { System.out.println("Call userMapper.list...");
                                                                    return userMapper.list();
                                                                }
                                                                @Override
                                                                public Integer add(User user) { System.out.println("Call userMapper.add...");
                                                                    return userMapper.add(user);
                                                                }
                                                                @Override
                                                                public Integer update(User user) { System.out.println("Call userMapper.update...");
                                                                    return userMapper.update(user);
                                                                }
                                                                @Override
                                                                public Integer deleteById(Long id) { System.out.println("Call userMapper.deleteById...");
                                                                    return userMapper.deleteById(id);
                                                                }
                                                                @Override
                                                                public User getById(Long id) { System.out.println("Call userMapper.getById...");
                                                                    return userMapper.getById(id);
                                                                }
                                                            }
                                                            

                                                            Controller层:

                                                            @RestController
                                                            @RequestMapping("/user")
                                                            public class UserController { @Autowired
                                                                private UserService userService;
                                                                @GetMapping("/list")
                                                                public List list() { System.out.println("Call UserService.list");
                                                                    return userService.list();
                                                                }
                                                                @GetMapping("/info")
                                                                public User getUserById(Long id) { System.out.println("Call UserService.getUserById");
                                                                    return userService.getById(id);
                                                                }
                                                                @PostMapping("/add")
                                                                public Integer add(@RequestBody User user) { System.out.println("Call UserService.add");
                                                                    return userService.add(user);
                                                                }
                                                                @PostMapping("/update")
                                                                public Integer update(@RequestBody User user) { System.out.println("Call UserService.update");
                                                                    return userService.update(user);
                                                                }
                                                                @PostMapping("/delete")
                                                                public Integer delete(Long id) { System.out.println("Call UserService.delete");
                                                                    return userService.deleteById(id);
                                                                }
                                                            }
                                                            

                                                            6、分层测试

                                                            6.1、Dao层测试

                                                            在UserMapperTest测试类中可以直接使用@Autowired来装配UserMapper这个Bean。而且,@SpringBootTest注解会自动帮我们完成启动一个Spring容器ApplicationContext,然后连接数据库,执行一套完整的业务逻辑。

                                                            import org.junit.jupiter.api.Test;
                                                            import org.springframework.beans.factory.annotation.Autowired;
                                                            import org.springframework.boot.test.context.SpringBootTest;
                                                            import pers.zhang.entity.User;
                                                            import java.util.List;
                                                            import static org.junit.jupiter.api.Assertions.*;
                                                            import static org.assertj.core.api.Assertions.*;
                                                            @SpringBootTest
                                                            class UserMapperTest { /**
                                                                 * 数据库user表内容如下:
                                                                 *
                                                                 *  id  |  name  |  age  |  height  |
                                                                 *  1      tom      18       1.77
                                                                 *  2      jerry    22       1.83
                                                                 *  3      mike     24       1.79
                                                                 */
                                                                @Autowired
                                                                private UserMapper userMapper;
                                                                @Test
                                                                void list() { List list = userMapper.list();
                                                                    assertThat(list.size()).isEqualTo(3);
                                                                    assertThat(list).extracting("id", "name", "age", "height")
                                                                            .contains(
                                                                                    tuple(1L, "tom", 18, 1.77),
                                                                                    tuple(2L, "jerry", 22, 1.83),
                                                                                    tuple(3L, "mike", 24, 1.79)
                                                                            );
                                                                }
                                                                @Test
                                                                void add() { User user = new User();
                                                                    user.setName("zhangsan");
                                                                    user.setAge(30);
                                                                    user.setHeight(1.66);
                                                                    Integer effectRows = userMapper.add(user);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                }
                                                                @Test
                                                                void update() { User user = new User();
                                                                    user.setName("zhangsan");
                                                                    user.setAge(33);
                                                                    user.setHeight(1.88);
                                                                    user.setId(7L);
                                                                    Integer effectRows = userMapper.update(user);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                }
                                                                @Test
                                                                void deleteById() { Integer effectRows = userMapper.deleteById(7L);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                }
                                                                @Test
                                                                void getById() { User expect = new User();
                                                                    expect.setId(1L);
                                                                    expect.setName("tom");
                                                                    expect.setAge(18);
                                                                    expect.setHeight(1.77);
                                                                    User user = userMapper.getById(1L);
                                                                    assertThat(user).isEqualTo(expect);
                                                                }
                                                            }
                                                            

                                                            6.2、Service层测试

                                                            上面的测试代码是连接真实数据库来执行真实的Dao层数据库查询逻辑。而在实际开发中,有时候需要独立于数据库进行Service层逻辑的开发。这个时候就可以直接把数据库Dao层代码Mock掉。

                                                            import org.junit.jupiter.api.BeforeEach;
                                                            import org.junit.jupiter.api.Test;
                                                            import org.mockito.*;
                                                            import pers.zhang.entity.User;
                                                            import pers.zhang.mapper.UserMapper;
                                                            import java.util.ArrayList;
                                                            import java.util.List;
                                                            import static org.assertj.core.api.Assertions.*;
                                                            import static org.mockito.Mockito.*;
                                                            class UserServiceImplTest { //Mock掉Dao层
                                                                @Mock
                                                                private UserMapper userMapper;
                                                                //把Mock掉的Dao层注入Service
                                                                @InjectMocks
                                                                private UserServiceImpl userService;
                                                                @BeforeEach
                                                                void setup() { //开启Mockito注解
                                                                    MockitoAnnotations.openMocks(this);
                                                                }
                                                                @Test
                                                                void list() { List users = new ArrayList<>();
                                                                    users.add(new User(10L, "zhangsan", 18, 1.77));
                                                                    users.add(new User(11L, "lisi", 22, 1.83));
                                                                    //打桩
                                                                    when(userMapper.list()).thenReturn(users);
                                                                    //调用
                                                                    List list = userService.list();
                                                                    list.forEach(System.out::println);
                                                                    //验证
                                                                    verify(userMapper, times(1)).list();
                                                                }
                                                                @Test
                                                                void add() { User user = new User(1L, "tom", 21, 1.80);
                                                                    //打桩
                                                                    when(userMapper.add(isA(User.class))).thenReturn(1);
                                                                    //调用
                                                                    Integer effectRows = userService.add(user);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                    //验证
                                                                    verify(userMapper, times(1)).add(user);
                                                                }
                                                                @Test
                                                                void update() { User user = new User(2L, "tom", 21, 1.80);
                                                                    //打桩
                                                                    when(userMapper.update(argThat(u -> { return u != null && u.getId() != null;
                                                                    }))).thenReturn(1);
                                                                    //调用
                                                                    Integer effectRows = userService.update(user);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                    //验证
                                                                    verify(userMapper, times(1)).update(user);
                                                                }
                                                                @Test
                                                                void deleteById() { //打桩
                                                                    when(userMapper.deleteById(anyLong())).thenReturn(1);
                                                                    //调用
                                                                    Integer effectRows = userService.deleteById(999L);
                                                                    assertThat(effectRows).isEqualTo(1);
                                                                    //验证
                                                                    verify(userMapper, times(1)).deleteById(999L);
                                                                }
                                                                @Test
                                                                void getById() { User user = new User(1L, "xxx", 40, 1.92);
                                                                    //打桩
                                                                    when(userMapper.getById(1L)).thenReturn(user);
                                                                    //调用
                                                                    User actual = userService.getById(1L);
                                                                    assertThat(actual).isInstanceOf(User.class);
                                                                    //验证
                                                                    verify(userMapper, times(1)).getById(1L);
                                                                }
                                                            }
                                                            

                                                            输出:

                                                            Call userMapper.update...
                                                            Call userMapper.getById...
                                                            Call userMapper.add...
                                                            Call userMapper.list...
                                                            User(id=10, name=zhangsan, age=18, height=1.77)
                                                            User(id=11, name=lisi, age=22, height=1.83)
                                                            Call userMapper.deleteById...
                                                            

                                                            6.3、Controller层测试

                                                            spring-boot-starter-test提供了MockMvc对Controller测试功能的强大支持。

                                                            import org.junit.jupiter.api.BeforeEach;
                                                            import org.junit.jupiter.api.Test;
                                                            import org.mockito.InjectMocks;
                                                            import org.mockito.Mock;
                                                            import org.mockito.MockitoAnnotations;
                                                            import org.springframework.http.MediaType;
                                                            import org.springframework.test.web.servlet.MockMvc;
                                                            import org.springframework.test.web.servlet.MvcResult;
                                                            import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
                                                            import org.springframework.test.web.servlet.setup.MockMvcBuilders;
                                                            import pers.zhang.entity.User;
                                                            import pers.zhang.service.UserService;
                                                            import java.util.Arrays;
                                                            import static org.mockito.Mockito.*;
                                                            import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
                                                            import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
                                                            class UserControllerTest { private MockMvc mockMvc;
                                                                @Mock
                                                                private UserService userService;
                                                                @InjectMocks
                                                                private UserController userController;
                                                                @BeforeEach
                                                                void setup() { //开启Mockito注解
                                                                    MockitoAnnotations.openMocks(this);
                                                                    //初始化MockMvc,将UserController注入其中
                                                                    mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
                                                                }
                                                                @Test
                                                                void list() throws Exception { //打桩
                                                                    when(userService.list()).thenReturn(
                                                                            Arrays.asList(
                                                                                    new User(1L, "tom", 18, 1.77),
                                                                                    new User(2L, "jerry", 22, 1.88)
                                                                            ));
                                                                    //调用
                                                                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/list")
                                                                                .contentType(MediaType.APPLICATION_JSON)
                                                                                .accept(MediaType.APPLICATION_JSON))
                                                                            .andExpect(status().isOk())
                                                                            .andDo(print())
                                                                            .andReturn();
                                                                    System.out.println(mvcResult.getResponse().getContentAsString());
                                                                    //验证
                                                                    verify(userService, times(1)).list();
                                                                }
                                                                @Test
                                                                void getUserById() throws Exception { //打桩
                                                                    User user = new User(1L, "tom", 18, 1.77);
                                                                    when(userService.getById(anyLong())).thenReturn(user);
                                                                    //调用
                                                                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/info")
                                                                                    .accept(MediaType.APPLICATION_JSON)
                                                                                    .contentType(MediaType.APPLICATION_JSON)
                                                                                    .param("id", "1"))
                                                                            .andExpect(status().isOk())
                                                                            .andDo(print())
                                                                            .andReturn();
                                                                    System.out.println(mvcResult.getResponse().getContentAsString());
                                                                    //验证
                                                                    verify(userService, times(1)).getById(1L);
                                                                }
                                                                @Test
                                                                void add() throws Exception { User user = new User();
                                                                    user.setName("jerry");
                                                                    user.setAge(22);
                                                                    user.setHeight(1.74);
                                                                    //打桩
                                                                    when(userService.add(isA(User.class))).thenReturn(1);
                                                                    //调用
                                                                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/add")
                                                                                    .accept(MediaType.APPLICATION_JSON)
                                                                                    .contentType(MediaType.APPLICATION_JSON)
                                                                                    .content("{\"name\": \"jerry\", \"age\": 22, \"height\": 1.74}"))
                                                                            .andExpect(status().isOk())
                                                                            .andDo(print())
                                                                            .andReturn();
                                                                    System.out.println(mvcResult.getResponse().getContentAsString());
                                                                    //验证
                                                                    verify(userService, times(1)).add(user);
                                                                }
                                                                @Test
                                                                void update() throws Exception { User user = new User(1L, "tom", 18, 1.77);
                                                                    //打桩
                                                                    when(userService.update(isA(User.class))).thenReturn(1);
                                                                    //调用
                                                                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/update")
                                                                                    .accept(MediaType.APPLICATION_JSON)
                                                                                    .contentType(MediaType.APPLICATION_JSON)
                                                                                    .content("{\"id\": 1, \"name\": \"tom\", \"age\": 18, \"height\": 1.77}"))
                                                                            .andExpect(status().isOk())
                                                                            .andDo(print())
                                                                            .andReturn();
                                                                    System.out.println(mvcResult.getResponse().getContentAsString());
                                                                    //验证
                                                                    verify(userService, times(1)).update(user);
                                                                }
                                                                @Test
                                                                void delete() throws Exception { //打桩
                                                                    when(userService.deleteById(anyLong())).thenReturn(1);
                                                                    //调用
                                                                    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/delete")
                                                                                    .accept(MediaType.APPLICATION_JSON)
                                                                                    .param("id", "1"))
                                                                            .andExpect(status().isOk())
                                                                            .andDo(print())
                                                                            .andReturn();
                                                                    System.out.println(mvcResult.getResponse().getContentAsString());
                                                                    //验证
                                                                    verify(userService, times(1)).deleteById(1L);
                                                                }
                                                            }
                                                            

                                                            7、JSON接口测试

                                                            使用JsonPath可以像JavaScript语法一样方便地进行JSON数据返回的访问操作。

                                                            import org.hamcrest.Matchers;
                                                            import org.junit.jupiter.api.Test;
                                                            import org.springframework.beans.factory.annotation.Autowired;
                                                            import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
                                                            import org.springframework.boot.test.context.SpringBootTest;
                                                            import org.springframework.http.MediaType;
                                                            import org.springframework.test.web.servlet.MockMvc;
                                                            import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
                                                            import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
                                                            import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
                                                            @SpringBootTest
                                                            @AutoConfigureMockMvc
                                                            class JsonControllerTest { @Autowired
                                                                private MockMvc mockMvc;
                                                                /**
                                                                 * 数据库user表内容如下:
                                                                 *
                                                                 *  id  |  name  |  age  |  height  |
                                                                 *  1      tom      18       1.77
                                                                 *  2      jerry    22       1.83
                                                                 *  3      mike     24       1.79
                                                                 */
                                                                @Test
                                                                void list() throws Exception { mockMvc.perform(MockMvcRequestBuilders
                                                                                .get("/user/list")
                                                                                .accept(MediaType.APPLICATION_JSON))
                                                                            //响应码200
                                                                            .andExpect(MockMvcResultMatchers.status().isOk())
                                                                            //json数组长度为3
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$.length()", Matchers.equalTo(3)))
                                                                            //name包含指定值
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$..name", Matchers.contains("tom", "jerry", "mike")))
                                                                            .andDo(MockMvcResultHandlers.print());
                                                                }
                                                                @Test
                                                                void getUserById() throws Exception { mockMvc.perform(MockMvcRequestBuilders
                                                                                .get("/user/info")
                                                                                .accept(MediaType.APPLICATION_JSON)
                                                                                .param("id", "1"))
                                                                            .andExpect(MockMvcResultMatchers.status().isOk())
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("tom")))
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$.age", Matchers.equalTo(18)))
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$.height", Matchers.equalTo(1.77)))
                                                                            .andDo(MockMvcResultHandlers.print());
                                                                }
                                                                @Test
                                                                void add() throws Exception { mockMvc.perform(MockMvcRequestBuilders
                                                                                .post("/user/add")
                                                                                .contentType(MediaType.APPLICATION_JSON)
                                                                                .accept(MediaType.APPLICATION_JSON)
                                                                                .content("{\"name\": \"zhangsan\", \"age\":  40, \"height\": 1.76}"))
                                                                            .andExpect(MockMvcResultMatchers.status().isOk())
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1)))
                                                                            .andDo(MockMvcResultHandlers.print());
                                                                }
                                                                @Test
                                                                void update() throws Exception { mockMvc.perform(MockMvcRequestBuilders
                                                                                .post("/user/update")
                                                                                .contentType(MediaType.APPLICATION_JSON)
                                                                                .accept(MediaType.APPLICATION_JSON)
                                                                                .content("{\"id\": 9, \"name\": \"lisi\", \"age\":  44, \"height\": 1.76}"))
                                                                            .andExpect(MockMvcResultMatchers.status().isOk())
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1)))
                                                                            .andDo(MockMvcResultHandlers.print());
                                                                }
                                                                @Test
                                                                void delete() throws Exception { mockMvc.perform(MockMvcRequestBuilders
                                                                                .post("/user/delete")
                                                                                .contentType(MediaType.APPLICATION_JSON)
                                                                                .accept(MediaType.APPLICATION_JSON)
                                                                                .param("id", "9"))
                                                                            .andExpect(MockMvcResultMatchers.status().isOk())
                                                                            .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1)))
                                                                            .andDo(MockMvcResultHandlers.print());
                                                                }
                                                            }