SpringBoot Test详解

目录

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依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

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)可以佐证。

java 复制代码
@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、集成测试

java 复制代码
//指定@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,提供一个方法,返回一个字符串:

java 复制代码
@RestController
public class TestController {

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

单元测试:

java 复制代码
@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());
    }
}

运行测试输出:

java 复制代码
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的功能,源码如下:

java 复制代码
@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。源码如下:

java 复制代码
@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文件中的相关配置如下:

java 复制代码
# 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相关功能提供自动配置的。

java 复制代码
@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的构造器,其主要有两个实现:StandaloneMockMvcBuilderDefaultMockMvcBuilder,前者继承了后者。

  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;其主要有两个子类MockHttpServletRequestBuilderMockMultipartHttpServletRequestBuilder(如文件上传使用),即用来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来指定不同格式的请求信息:

java 复制代码
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<? super T> matcher):根据响应体计算给定的JsonPath表达式,并使用给定的Hamcrest Matcher断言结果值
  • jsonPath(String expression, Matcher<? super T> 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<String, String> 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、业务代码

实体类:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private Double height;
}

Dao层:

java 复制代码
@Mapper
public interface UserMapper {

    List<User> list();

    Integer add(User user);

    Integer update(User user);

    Integer deleteById(Long id);

    User getById(Long id);
}

UserMapper.xml:

xml 复制代码
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace= "pers.zhang.mapper.UserMapper" >

    <insert id="add">
        INSERT INTO user (name, age, height)
        VALUES (#{name}, #{age}, #{height})
    </insert>

    <update id="update">
        UPDATE user SET name = #{name}, age = #{age}, height = #{height}
        WHERE id = #{id}
    </update>

    <delete id="deleteById">
        DELETE FROM user
        WHERE id = #{id}
    </delete>

    <select id = "list" resultType="pers.zhang.entity.User">
        SELECT id, name, age, height
        FROM user;
    </select>

    <select id="getById" resultType="pers.zhang.entity.User">
        SELECT id, name, age, height
        FROM user
        WHERE id = #{id}
    </select>

</mapper>

Service层:

java 复制代码
public interface UserService {
    List<User> list();

    Integer add(User user);

    Integer update(User user);

    Integer deleteById(Long id);

    User getById(Long id);
}
java 复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> 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层:

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/list")
    public List<User> 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,然后连接数据库,执行一套完整的业务逻辑。

java 复制代码
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<User> 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掉。

java 复制代码
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<User> 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<User> 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);
    }
}

输出:

java 复制代码
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测试功能的强大支持。

java 复制代码
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数据返回的访问操作。

java 复制代码
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());
    }
}
相关推荐
bing_1587 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
三个蔡9 小时前
Java求职者面试:从Spring Boot到微服务的技术深度探索
java·大数据·spring boot·微服务·kubernetes
小鸡脚来咯9 小时前
SpringBoot 常用注解通俗解释
java·spring boot·后端
创码小奇客9 小时前
MongoDB 事务:数据世界的守护者联盟全解析
spring boot·mongodb·trae
中国lanwp10 小时前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
cherishSpring10 小时前
在windows使用docker打包springboot项目镜像并上传到阿里云
spring boot·docker·容器
苹果酱056711 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
慧一居士12 小时前
Kafka HA集群配置搭建与SpringBoot使用示例总结
spring boot·后端·kafka
uncofish13 小时前
springboot不连接数据库启动(原先连接了mysql数据库)
数据库·spring boot·mysql