我正在参加「启航计划」

Mock的概念

在软件开发中提及”mock”,一般理解为模仿目标。它能够用来对体系、组件或类进行阻隔。在测验过程中,咱们一般重视测验目标本身的功用和行为,而对测验目标触及的一些依靠,只是重视它们与测验目标之间的交互(比方是否调用、何时调用、调用的参数、调用的次数和顺序,以及回来的成果或发生的反常等),并不重视这些被依靠目标如何履行这次调用的具体细节。因而,Mock 机制就是运用 Mock 目标代替实在的依靠目标,并模仿实在场景来展开测验工作。

运用 Mock 目标完成依靠关系测验的示意图如下所示:

SpringBoot单元测试之常见框架和注解

SpringBootTest包导入的组件

比方 JUnit、JSON Path、AssertJ、Mockito、Hamcrest 等,这儿咱们有必要对这些组件进行展开说明。

  • JUnit:JUnit 是一款十分盛行的依据 Java 语言的单元测验结构,在咱们的课程中首要运用该结构作为根底的测验结构。
  • JSON Path:类似于 XPath 在 XML 文档中的定位,JSON Path 表达式一般用来检索途径或设置 JSON 文件中的数据。
  • AssertJ:AssertJ 是一款强壮的流式断语东西,它需求恪守 3A 中心原则,即 Arrange(初始化测验目标或准备测验数据)——> Actor(调用被测办法)——>Assert(履行断语)。
  • Mockito:Mockito 是 Java 世界中一款盛行的 Mock 测验结构,它首要运用简练的 API 完成模仿操作。在施行集成测验时,咱们将大量运用到这个结构。
  • Hamcrest:Hamcrest 供给了一套匹配器(Matcher),其间每个匹配器的规划用于履行特定的比较操作。
  • JSONassert:JSONassert 是一款专门针对 JSON 供给的断语结构。
  • Spring Test & Spring Boot Test:为 Spring 和 Spring Boot 结构供给的测验东西。

Spring单元测验编写

大致能够分为如下三类:

  1. 单元测验:一般面向办法,编写一般业务代码时,测验本钱较大。触及到的注解有@Test
  2. 切片测验:一般面向难于测验的鸿沟功用,介于单元测验和功用测验之间。触及到的注解有@RunWith@WebMvcTest等。
  3. 功用测验:一般面向某个完整的业务功用,一起也能够运用切面测验中的mock才能,引荐运用。触及到的注解有@RunWith@SpringBootTest等。

初始化测验环境

@SpringBootTest
@RunWith(SpringRunner.class)

@SpringBootTest注解

默许情况下,@SpringBootTest不会发动嵌入式服务器。您能够运用 @SpringBootTest 的 webEnvironment 特点进一步完善测验的运转办法:

  • MOCK: 加载 WebApplicationContext 并供给一个 Mock 的 Servlet 环境,此时内置的 Servlet 容器并没有正式发动,能够合作@AutoConfigureMockMvc@AutoConfigureWebTestClient结合运用。
    • 在多数场景下,一个实在的 Servlet 环境对于测验而言过于重量级,经过 MOCK 环境则能够缓解这种环境约束所带来的本钱和挑战。
  • RANDOM_PORT: 加载 EmbeddedWebApplicationContext 并供给一个实在的 Servlet 环境,然后运用一个随机端口发动内置容器。
  • DEFINED_PORT: 这个装备也是经过加载 EmbeddedWebApplicationContext 供给一个实在的 Servlet 环境,但运用的是默许端口,假如没有装备端口就运用 8080。
  • NONE: 加载 ApplicationContext 但并不供给任何实在的 Servlet 环境

运用命令行参数

假如您的应用程序需求arguments,您能够运用@SpringBootTestargs特点注入它们

@SpringBootTest(args = "--app.test=one")
class ApplicationArgumentsExampleTests {
    @Test
    void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
        assertThat(args.getOptionNames()).containsOnly("app.test");
        assertThat(args.getOptionValues("app.test")).containsOnly("one");
    }
}

常见测验注解总结

SpringBoot单元测试之常见框架和注解

主动装备类型的注解(@AutoConfigure*)

  1. @AutoConfigureJsonTesters 主动装备JsonTester
  2. @AutoConfigureTestEntityManager 主动装备TestEntityManager
  3. @AutoConfigureMockRestServiceServer 主动装备 MockRestServiceServer
  4. @AutoConfigureWebTestClient 主动装备 WebTestClient
  5. @AutoConfigureMockMvc 主动装备 MockMvc
  6. @AutoConfigureTestDatabase 主动装备Test Database,能够运用内存数据库

业务回滚@Transactional

独自的@Transactional是回滚业务,在增加@Transactional的情况下假如要提交业务,只需求增加@Rollback(false);别的因为@Rollback能够用在办法上,所以一个测验类中,咱们能够完成部分测验办法用@Rollback回滚业务,部分测验办法用@Rollback(false)来提交业务。

TestRestTemplate、WebTestClient 和 MockMvc 有什么区别?

尽管这三位候选者都服务于类似的目标:调用咱们的 HTTP 端点并验证呼应

MockMvc 与模仿 servlet 环境交互的 Fluent API,有支撑验证服务器端烘托视图端点的模型或视图称号的 API。

    • 运用MockMvc,咱们不需求发动咱们的嵌入式 servlet 容器(例如Tomcat)。因而咱们不占用任何端口。咱们运用该MockMvc实例与这个模仿环境交互,而不发动真正的 HTTP 通讯。

WebTestClient 最初是用于调用和验证 Spring WebFlux 端点的测验东西。但是,咱们也能够运用它为正在运转的servlet容器或MockMvc编写测验。

TestRestTemplate 经过HTTP测验和验证正在运转的servlet容器的控制器端点,API不太流通。

SpringBoot单元测试之常见框架和注解

MockMvc东西类

MockMvc 类供给的根底办法分为以下 6 种,下面一一对应来看下。

  • Perform:履行一个 RequestBuilder 恳求,会主动履行 SpringMVC 流程并映射到相应的 Controller 进行处理。
  • get/post/put/delete:声明发送一个 HTTP 恳求的办法,依据 URI 模板和 URI 变量值得到一个 HTTP 恳求,支撑 GET、POST、PUT、DELETE 等 HTTP 办法。
  • param:增加恳求参数,发送 JSON 数据时将不能运用这种办法,而应该采用 @ResponseBody 注解。
  • andExpect:增加 ResultMatcher 验证规矩,经过对回来的数据进行判断来验证 Controller 履行成果是否正确。
  • andDo:增加 ResultHandler 成果处理器,比方调试时打印成果到控制台。
  • andReturn:最终回来相应的 MvcResult,然后履行自定义验证或做异步处理。

测验案例

  @Test
  void pageMessageCenterByUserId(@Autowired MockMvc mvc) throws Exception {
    MvcResult mvcResult = mvc.perform(get("xxx")
      // 恳求数据类型
      .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
      // 回来数据类型
      .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
      // session会话目标
      .session(session)
      // URL传参
      .param("key", "value")
      // body传参
      .content(json))
      // 验证参数
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.code").value(0))
      // 打印恳求和呼应体
      .andDo(MockMvcResultHandlers.print());
    // 打印呼应body
    System.out.println(mvcResult.getResponse().getContentAsString());
  }

测验下载文件

运用mockMvc测验下载文件时,需求注意controller办法的回来值需求为void,不然会报HttpMessageNotWritableException的反常错误

@Test
@WithUserDetails("admin")
@DisplayName("测验下载excel文件")
void downExcel() throws Exception {
    mockMvc.perform(get("/system/operate/export/excel")
                    .accept(MediaType.APPLICATION_OCTET_STREAM)
                    .param("startTime", "2022-11-22 10:51:25")
                    .param("endTime", "2022-11-23 10:51:25"))
    .andExpect(status().isOk())
    .andDo((result) -> {
        String contentDisposition = result.getResponse().getHeader("Content-Disposition");
        String fileName = URLDecoder.decode(contentDisposition.split("=")[1]);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(result.getResponse().getContentAsByteArray());
        String basePath = System.getProperty("user.dir");
        // 保存为文件
        File file = new File(basePath + "/" + fileName);
        FileUtil.del(file);
        FileOutputStream outputStream = new FileOutputStream(file);
        StreamUtils.copy(inputStream, outputStream);
        outputStream.close();
        inputStream.close();
    });
}

SpringSecurity 单元测验

Spring Security 也供给了专门用于测验安全性功用的 spring-security-test 组件,如下所示:

<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-test</artifactId>
 <scope>test</scope>
</dependency>

测验用户/认证

运用@WithMockUser@WithUserDetails@WithAnonymousUser等注解

@WithAnonymousUser是用来模仿一种特别的用户,也被叫做匿名用户。假如有测验匿名用户的需求,能够直接运用该注解。

@WithMockUser注解能够帮咱们在Spring Security安全上下文中模仿一个用户。

  • 尽管 @WithMockUser是一种十分便利的办法,但可能并非在一切情况下都凑效。有时候你魔改了一些东西使得安全上下文的验证机制发生了改变,比方你定制了UserDetails,这一类注解就不好用了。可是经过UserDetailsService 加载的用户往往仍是牢靠的。

@WithUserDetails就派上了用场,它会依据传入的用户名调用UserDetailsService 的loadUserByUsername办法查找用户并加载到安全上下文中

测验 CSRF

下述 csrf() 办法的作用就是在恳求中增加 CSRF Token

@Test
public void testHelloUsingPOSTWithCSRF() throws Exception {
     mvc.perform(post("/hello").with(csrf()))
            .andExpect(status().isOk());
}

测验CORS

咱们经过 MockMvc 建议恳求,然后对呼应的音讯头进行验证即可,测验用例如下所示:

@SpringBootTest
@AutoConfigureMockMvc
public class MainTests {
    @Autowired
    private MockMvc mvc;
    @Test
    public void testCORSForTestEndpoint() throws Exception {
        mvc.perform(options("/hello")
                .header("Access-Control-Request-Method", "POST")
                .header("Origin", "http://www.test.com")
        )
        .andExpect(header().exists("Access-Control-Allow-Origin"))
        .andExpect(header().string("Access-Control-Allow-Origin", "*"))
        .andExpect(header().exists("Access-Control-Allow-Methods"))
        .andExpect(header().string("Access-Control-Allow-Methods", "POST"))
        .andExpect(status().isOk());
    }
}

能够看到,针对 CORS 装备,咱们分别获取了呼应成果的”Access-Control-Allow-Origin”和”Access-Control-Allow-Methods”音讯头并进行了验证。

SpringBoot加载测验专用特点

用args增加临时命令行参数

@SpringBootTest(args = {"--test.prop=test"})

激活指定装备文件

@ActiveProfiles("pro")

加载其他装备文件

@TestPropertySource(locations = "classpath:config-test.properties")

常见问题

Junit版别问题

假如运用的是JUnit 4,需求增加@RunWith(SpringRunner.class)到测验中,不然会报错。假如您运用的是JUnit 5,则无需增加,因为@SpringBootTest中已经增加了@ExtendWith(SpringExtension.class),测验类中不需求在写@Runwith的时候,能够在pom中扫除junit4的依靠。

断语

JUnit5 断语运用org.junit.jupiter.api.Assertions的静态办法。 除此之外还能够运用 AssertJ(org.assertj.core.api.AssertionsassertThat办法)。

单元测验中@Transactional不回滚的问题

docs.spring.io/spring-boot…

If your test is@Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with eitherRANDOM_PORTorDEFINED_PORTimplicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.

假如您的测验是@Transactional,默许情况下,它会在每个测验办法结束时回滚业务。但是,运用RANDOM_PORT或DEFINED_PORT的这种供给了一个真正的servlet环境的情况下,HTTP客户端和服务器在独自的线程中运转,因而会在独自的业务中运转。这种情况下,服务器上建议的任何业务都不会回滚。

you should use caution if Spring-managed or application-managed transactions are configured with any propagation type other than REQUIRED or SUPPORTS (see the discussion on transaction propagation for details).

假如Spring办理的或应用办理的业务被装备为REQUIRED或SUPPORTS以外的任何传达类型,你应该谨慎行事,因为它们都需求遵从业务的传达办法,也会呈现业务不会滚的问题,比方你用了REQUIRED_NEW的话就跟单元测验中的业务不在一个业务中了,所以无法回滚。

Maven打包时单元测验不运转

能够看我的这篇文章SpringBoot单元测验Maven打包时不运转的问题