3

SpringBootTest 测试工具

以下内容,翻译自官方文档,并结合了学习过程的demo。


Spring Boot提供了许多实用程序和注解,帮助测试应用程序。测试支持由两个模块提供:spring-boot-test 包含核心项,spring-boot-test-autoconfigure 支持测试的自动配置。

大多数开发人员使用 spring-boot-starter-test,它同时导入 SpringBoot 测试模块以及JUnit Jupiter、AssertJ、Hamcrest和许多其他有用的库。

此文使用当前最新稳定版本: SpringBoot 2.2.2.RELEASE
此 starter 还带来了 vintage  引擎,因此可以同时运行JUnit 4和JUnit 5测试。如果已经将测试迁移到JUnit5,那么应该排除JUnit4支持,如下例所示:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <exclusions>
        <exclusion>
          <!-- 此模块兼容junit4 和 junit 5,此示例直接使用 junit5 -->
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
      <scope>test</scope>
    </dependency>

Test Scope Dependencies

spring-boot-starter-test (依赖 scopetest)包含以下库:

  • JUnit 5:包含兼容 JUnit 4,Java 应用程序单元测试的事实标准
  • Spring Test 和 SpringBootTest:对Spring Boot应用程序的公共和集成测试支持。
  • AssertJ:流式断言库
  • Hamcrest:匹配对象库
  • Mockito:Java 模拟框架
  • JSONassert:JSON 断言库
  • JsonPath:JSON XPath

测试 Spring 应用

依赖注入的一个主要优点是它应该使代码更容易进行单元测试。可以使用新的操作符实例化对象,甚至不涉及Spring。也可以使用模拟对象而不是实际依赖项。

通常,您需要超越单元测试,开始集成测试(使用Spring ApplicationContext)。能够在不需要部署应用程序或连接到其他基础结构的情况下执行集成测试是非常有用的。

Spring框架包含了一个专门的集成测试模块。可以直接向 org.springframework:spring 测试声明一个依赖项,或者使用 spring-boot-starter-test

如果以前没有使用过 spring-test 模块,那么应该从阅读spring框架参考文档的相关部分开始。

测试 SpringBoot 应用

SpringBoot 应用程序是 Spring ApplicationContext,因此除了使用普通的Spring上下文之外,不必做任何特别的事情来测试它。

默认情况下,只有在使用 SpringApplication 创建 Spring Boot时,它的外部属性、日志记录和其他特性才会安装在上下文中。

SpringBoot 提供了一个 @SpringBootTest 注解,当需要SpringBoot 特性时,它可以作为标准 spring-test @ContextConfiguration 注解的替代。注解的工作方式是通过 SpringApplication 创建测试中使用的ApplicationContext。除了 @SpringBootTest之外,还提供了一些其他注解,用于测试应用程序的更具体的部分。

如果使用的是JUnit4,不要忘记将 @RunWith(SpringRunner.class) 添加到测试中,否则注解将被忽略。如果使用的是JUnit5,则不需要添加与 @SpringBootTest 和 其他已经使用的注解等效的 @ExtendWith(SpringExtension.class)

默认情况下,@SpringBootTest 不会启动服务器。可以使用 @SpringBootTestwebEnvironment 属性进一步优化测试的运行方式:

  • MOCK(默认):加载 web ApplicationContext 并提供模拟web环境。使用此注解时,嵌入式服务器未启动。如果类路径上没有可用的web环境,则此模式会透明地回退到创建常规的非web ApplicationContext。它可以与 @AutoConfigureMockMvc@AutoConfigureWebTestClient 结合使用,对web应用程序进行基于模拟的测试。
  • RANDOM_PORT:加载 WebServerApplicationContext 并提供真正的web环境。嵌入式服务器启动并在随机端口上监听。
  • DEFINED_PORT:加载 WebServerApplicationContext 并提供真正的web环境。嵌入式服务器将启动并在定义的端口(从 application.properties)或默认端口8080上监听。
  • NONE:使用 SpringApplication 加载 ApplicationContext,但不提供任何web环境(mock或其他)。

如果测试是 @Transactional,那么默认情况下,它会在每个测试方法结束时回滚事务。然而,由于对随机端口或定义的端口使用这种安排隐式地提供了一个真正的servlet环境,HTTP客户机和服务器在单独的线程中运行,因此在单独的事务中运行。在这种情况下,服务器上启动的任何事务都不会回滚。
@使用 webEnvironment=webEnvironment.RANDOM_PORT@SpringBootTest 也将在单独的随机端口上启动管理服务器,如果应用程序对管理服务器使用不同的端口。

检测 Web 应用类型

如果 Spring MVC 可用,那么将配置一个常规的基于MVC的应用程序上下文。如果只有Spring WebFlux,将检测并配置基于 WebFlux 的应用程序上下文。

如果两者都存在,则以Spring MVC为准。如果要在此方案中测试响应式web应用程序,则必须设置 spring.main.web-application-type 属性:

@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests { ... }

检测测试配置

Spring 测试框架中,可以使用 @ContextConfiguration(classes=…) 来指定加载那个 Spring @Configuration

在测试 Spring Boot 应用程序时,这通常不是必需的。Spring Boot的 @Test 类注解在没有显式定义主配置时自动搜索主配置。

搜索算法从包含测试的包开始工作,直到找到用 @SpringBootApplication@SpringBootConfiguration 注解的类为止。只要以合理的方式构造代码,通常都会找到主配置。

如果要自定义主配置,可以使用嵌套的 @TestConfiguration 类。与嵌套的 @Configuration 类(将用于替代应用程序的主配置)不同,嵌套的 @TestConfiguration 类是在应用程序的主配置额外使用的。

Spring的测试框架在测试之间缓存应用程序上下文。因此,只要您的测试共享相同的配置(无论如何发现),加载上下文的潜在耗时过程只发生一次。

排除测试配置

如果使用了 @SpringBootApplication@ComponentScan 扫描,针对特定测试的顶级配置类可能在任何地方都能够被获取到。

如前所述,@TestConfiguration 可用于测试的内部类,以自定义主要配置。当放置在顶级类上时,@TestConfiguration 表示 src/test/java 中的类不应该通过扫描来获取。然后,可以在需要时显式导入该类,如下例所示:

@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
    @Test
    void exampleTest() {
        //... 
    }
}

使用应用参数

如果应用程序需要参数,可以使用 @SpringBootTestargs 属性注入它们。

@SpringBootTest(args = "--app.name=test", webEnvironment = WebEnvironment.NONE)
public class ArgTests {

  @Test
  public void applicationArgsTest(@Autowired ApplicationArguments args) {
    assertThat(args.getOptionNames()).containsOnly("app.name");
    assertThat(args.getOptionValues("app.name")).containsOnly("test");
  }
}

用 mock environment测试

默认情况下,@SpringBootTest 不会启动服务器。如果在此模拟环境中有要测试的web端点,则可以另外配置MockMvc,如下例所示:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
public interface ConstantUtil {
  String body = "Hello World!";
}
@RestController
public class HelloWorldController {

  @RequestMapping
  public String helloWorld(){
    return ConstantUtil.body;
  }
}
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcTests {
  @Test
  public void test(@Autowired MockMvc mvc) throws Exception {
    mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string(ConstantUtil.body));
  }
}
如果您只想关注web层而不想启动一个完整的 ApplicationContext,可以考虑改用 @WebMvcTest

另外,可以配置 WebTestClient ,如下:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
/**
 * WebTestClient<br>
 * WebTestClient 属于 reactive,设置spring.main.web-application-type=reactive
 */
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
@AutoConfigureWebTestClient
public class MockWebTestClientTests {
  @Test
  public void test(@Autowired WebTestClient webTestClient) {
    webTestClient
        .get()
        .uri("/")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo(ConstantUtil.body);
  }
}
在模拟环境中进行测试通常比使用完整的Servlet容器运行要快。但是,由于模拟发生在Spring MVC层,依赖于低级Servlet容器行为的代码不能直接用MockMvc测试。
例如,Spring Boot的错误处理基于Servlet容器提供的“错误页”支持。这意味着,虽然可以按预期测试MVC层抛出和处理异常,但不能直接测试是否呈现了特定的自定义错误页。如果需要测试这些较低级别的关注点,可以按照下一节中的说明启动完全运行的服务器。

用运行中的server测试

如果需要启动完全运行的服务器,建议使用随机端口。如果使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT),则每次运行测试时都会随机选择一个可用端口。

@LocalServerPort 注解可用于将实际使用的端口注入测试。为了方便起见,需要对启动的服务器进行REST调用的测试还可以 @Autowire 一个 WebTestClient,它解析到正在运行的服务器的相关链接,并附带用于验证响应的专用API,如下例所示:

/**
 * 使用运行中的server<br>
 * 使用 webflux
 *
 * @author YiFeiXi
 */
@SpringBootTest(
    webEnvironment = WebEnvironment.RANDOM_PORT,
    properties = "spring.main.web-application-type=reactive")
public class RandomPortWebTestClientTests {
  @Test
  public void test(@Autowired WebTestClient webTestClient) {
    webTestClient
        .get()
        .uri("/")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo(ConstantUtil.body);
  }
}

此设置需要类路径上的 spring-webflux。如果您不能或不想添加webflux,Spring Boot 还提供了一个 TestRestTemplate 工具:

/**
 * 使用运行中的server<br>
 * 不使用 webflux
 *
 * @author YiFeiXi
 */
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortTestRestTemplateTests {
  @Test
  public void test(@Autowired TestRestTemplate restTemplate) {
    String body = restTemplate.getForObject("/", String.class);
    assertThat(body).isEqualTo(ConstantUtil.body);
  }
}

自定义 WebTestClient

要自定义 WebTestClient bean,需配置 WebTestClientBuilderCustomizer bean。使用用于创建 WebTestClientWebTestClient.Builder 调用任何此类bean。

使用 JMX

由于测试上下文框架缓存上下文的原因,默认情况下禁用JMX以防止相同的组件在同一域上注册。如果此类测试需要访问 MBeanServer,请考虑将其标记为dirty :

@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class SampleJmxTests {
    @Autowired
    private MBeanServer mBeanServer;
    @Test
    void exampleTest() {
        // ...
    }
}

虚拟和监控 Beans

运行测试时,有时需要在应用程序上下文中模拟某些组件。例如,可能有一个表面覆盖了一些在开发期间不可用的远程服务。当您希望模拟在实际环境中可能难以触发的故障时,模拟也很有用。

Spring Boot包含一个 @MockBean 注解,可以用来为 ApplicationContext 中的 bean 定义Mockito 模拟。可以使用注解添加新bean或替换单个现有bean定义。注解可以直接用于测试类、测试中的字段或 @Configuration 类和字段。在字段上使用时,创建的模拟的实例也会被注入。模拟bean在每个测试方法之后自动重置。

如果测试使用了Spring Boot的一个测试注解(例如 @SpringBootTest),则会自动启用此功能。要将此功能与其他排列一起使用,必须显式添加 listener,如下例所示:

@TestExecutionListeners(MockitoTestExecutionListener.class)

下面的示例用模拟实现替换现有的 testService bean:

@SpringBootTest
public class MockBeanTests {
  @Autowired private TestController testController;
  @MockBean private TestService testService;

  @Test
  public void test(){
    given(testService.hello()).willReturn("哈哈");
    String hello = testController.hello();
    assertThat(hello).isEqualTo("哈哈");
  }
}

@MockBean 不能用于模拟在应用程序上下文刷新期间执行的bean的行为。在执行测试时,应用程序上下文刷新已经完成,现在配置模拟行为已经太晚了。我们建议在这种情况下使用 @Bean 方法来创建和配置mock。

另外,您可以使用 @SpyBean 来使用 Mockito spy 以包装任何现有的bean

CGLIB代理,如为范围bean创建的代理,将代理方法声明为 final。这会阻止Mockito正常工作,因为它无法在默认配置中模拟或监视 final 方法。如果想模拟或监视这样的bean,可以通过将 org.mockito:mockito-inline 添加到应用程序的测试依赖项中来配置 Mockito 以使用其内联mock maker。这允许Mockito模拟和监视 final 方法。

虽然Spring的测试框架在测试之间缓存应用程序上下文,并为共享相同配置的测试重用上下文,但使用 @MockBean@SpyBean 会影响缓存键,这很可能会增加上下文的数量。

如果使用 @SpyBean 监视具有按名称引用参数的 @Cacheable 方法的bean,则必须使用 -parameters 编译应用程序。这确保了一旦bean被监视,参数名就可用于缓存基础结构。

自动配置测试

Spring Boot的自动配置系统对应用程序运行良好,但有时对测试来说可能有点太多了。它通常有助于只加载测试应用程序“片段”所需的配置部分。例如,您可能希望测试Spring MVC控制器是否正确映射了url,并且您不希望在这些测试中涉及数据库调用,或者您可能希望测试JPA实体,并且您对运行这些测试时的web层不感兴趣。

spring-boot-test-autoconfigure 模块包括许多注解,可以用来自动配置这些“片段”。它们中的每一个都以类似的方式工作,提供一个加载 ApplicationContext@…Test 注解和一个或多个可用于自定义自动配置设置的 @AutoConfigure… 注解。

每个片段将组件扫描限制为适当的组件,并加载一组非常有限的自动配置类。如果需要排除其中一个,大多数 @…Test 注解都提供 excludeAutoConfiguration 属性。或者,可以使用 @ImportAutoConfiguration#exclude

不支持在一个测试类中通过使用多个 @...Test 注解来包含多个“片段”。如果需要多个“片段”,请选择其中一个 @…Test 注解并手动包含其他“片段”的 @AutoConfigure… 注解。

也可以将 @AutoConfigure… 注解与标准的 @SpringBootTest 注解一起使用。如果对应用程序的“片段”不感兴趣,但需要一些自动配置的测试bean,则可以使用此组合。

自动配置 JSON 测试

要测试对象JSON序列化和反序列化是否按预期工作,可以使用 @JsonTest 注解。@JsonTest 自动配置可用的受支持JSON mapper,该 mapper 可以是以下库之一:

  • Jackson ObjectMapper,任何 @JsonComponent bean 和 任何 Jackson Module
  • Gson
  • Jsonb

可以在附录中找到@JsonTest启用的自动配置列表。

如果需要配置自动配置的元素,可以使用 @AutoConfigureJsonTesters 注解。

Spring Boot包括基于AssertJ的辅助程序,它们与 JSONAssert 和 JsonPath 库一起工作,检查JSON是否如预期的那样出现。JacksonTesterGsonTesterJsonbTesterBasicJsonTester 类可以分别用于Jackson、Gson、Jsonb和字符串。使用 @JsonTest 时,测试类上的任何辅助字段都可以 @Autowired。以下示例显示了Jackson的测试类:

/** @author YiFeiXi */
@JsonTest
public class JsonTests {

  @Autowired private JacksonTester<UserInfo> json;

  @Test
  public void testSerialize() throws Exception {
    UserInfo u = new UserInfo(1, "张", "三");
    assertThat(this.json.write(u))
        .isEqualToJson("{\"id\":1,\"firstName\":\"张\",\"lastName\": \"三\"}");
  }

  @Test
  public void testDeserialize() throws Exception {
    String content = "{\"firstName\":\"张\",\"lastName\": \"三\"}";
    assertThat(this.json.parseObject(content).getFirstName()).isEqualTo("张");
  }
}

JSON辅助类也可以直接用于标准单元测试。为此,如果不使用 @JsonTest,请在 @Before 方法中调用辅助类的 initFields 方法。

如果使用Spring Boot的基于AssertJ的辅助程序来 assert 给定JSON路径上的数值,则可能无法根据类型使用isEqualTo。相反,您可以使用 AssertJ 的 satisfies 来 assert 该值与给定条件匹配。例如,下面的示例 assert 实际数字是偏移量0.01内接近0.15的浮点值。

assertThat(json.write(message))
    .extractingJsonPathNumberValue("@.test.numberValue")
    .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));

自动配置 Spring MVC 测试

要测试Spring MVC controllers 是否按预期工作,使用 @WebMvcTest 注解。@WebMvcTest 自动配置Spring MVC基础结构,并将扫描的bean限制为@Controller、@ControllerAdvice、@JsonComponent、Converter、GenericConverter、Filter、HandlerInterceptor、WebMVCConfiguer和HandlerMethodArgumentResolver。使用此注解时,不扫描常规@Component bean。

附录中列出了@WebMvcTest启用的自动配置设置。

如果需要注册额外的组件,比如Jackson模块,可以在测试中使用@import导入额外的配置类。
通常,@WebMvcTest 仅限于一个控制器,并与 @MockBean 结合使用,为所需的协作者提供模拟实现。
@WebMvcTest还自动配置MockMvc。Mock MVC提供了一种快速测试MVC控制器的强大方法,无需启动完整的HTTP服务器。

还可以在非@WebMvcTest(如@SpringBootTest)中使用 @AutoConfigureMockMvc 对MockMvc进行自动配置。以下示例使用MockMvc:

/** @author YiFeiXi */
@WebMvcTest(TestController.class)
public class WebMvcTests {
  @Autowired private MockMvc mvc;
  @MockBean private TestService testService;

  @Test
  public void test() throws Exception {
    given(this.testService.hello()).willReturn("哈哈");
    this.mvc
        .perform(get("/hello").accept(MediaType.TEXT_PLAIN))
        .andExpect(status().isOk())
        .andExpect(content().string("哈哈"));
  }
}

如果需要配置自动配置的元素(例如,当应用servlet过滤器时),可以使用 @AutoConfigureMockMvc 注解中的属性。

如果使用HtmlUnit或Selenium,自动配置还提供HtmlUnit WebClient bean 或 Selenium WebDriver bean。以下示例使用HtmlUnit:

@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;
    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
                .willReturn(new VehicleDetails("Honda", "Civic"));
        HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
        assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
    }
}

默认情况下,Spring Boot 将 WebDriver bean 放置在一个特殊的“scope”中,以确保在每次测试之后驱动程序退出,并注入新的实例。如果不希望出现这种行为,可以将 @Scope("singleton") 添加到 WebDriver @Bean 定义中。

由Spring Boot创建的 webDriver 作用域将替换任何用户定义的同名作用域。如果定义了自己的webDriver作用域,则在使用 @WebMvcTest 时可能会发现它停止工作。

如果类路径上有Spring Security ,@WebMvcTest 还将扫描 WebSecurityConfigurer bean。可以使用Spring Security的测试支持,而不是完全禁用此类测试的安全性。有关如何使用Spring Security的 MockMvc 支持的更多详细信息,请参见 SpringSecurity 测试操作步骤部分。

自动配置 Spring WebFlux 测试

要测试Spring WebFlux controllers 是否按预期工作,可以使用 @WebFluxTest 注解。@WebFluxTest 自动配置Spring WebFlux基础结构,并将扫描的bean限制为@Controller、@ControllerAdvice、@JsonComponent、Converter、GenericConverter、WebFilter和WebFluxConfigurer。使用@WebFluxTest注解时,不扫描常规@Component bean。

附录中列出了 @WebFluxTest 启用的自动配置。

如果需要注册额外的组件,比如Jackson模块,可以在测试中使用@import导入额外的配置类。
通常,@WebFluxTest 仅限于一个控制器,并与@MockBean注解结合使用,为所需的协作者提供模拟实现。
@WebFluxTest还可以自动配置WebTestClient,这为快速测试WebFlux controllers提供了一种强大的方法,而无需启动完整的HTTP服务器。
还可以在非@WebFluxTest(如@SpringBootTest)中使用@AutoConfigureWebTestClient 注解,从而自动配置web测试客户端。以下示例显示同时使用@WebFluxTest和WebTestClient的类:

/**
 * WebTestClient<br>
 * WebTestClient 属于 reactive,设置spring.main.web-application-type=reactive
 */
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
@AutoConfigureWebTestClient
public class MockWebTestClientTests {
  @Test
  public void test(@Autowired WebTestClient webTestClient) {
    webTestClient
        .get()
        .uri("/")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo(ConstantUtil.body);
  }
}

此设置仅受WebFlux应用程序支持,因为在模拟的web应用程序中使用WebTestClient目前仅适用于WebFlux。

@WebFluxTest无法检测通过功能性web框架注册的路由。要在上下文中测试 RouterFunction bean,请考虑通过@Import或使用@SpringBootTest亲自导入RouterFunction。

@WebFluxTest无法检测通过 SecurityWebFilterChain 类型的@Bean注册的自定义安全配置。要将其包含在测试中,需要通过@import或使用@SpringBootTest导入注册bean的配置。

自动配置 Data JPA 测试

可以使用 @DataJpaTest 注解来测试JPA应用程序。默认情况下,它扫描@Entity类并配置Spring Data JPA repositories。如果类路径上有可用的嵌入式数据库,它也会配置一个。常规@Component bean不会加载到ApplicationContext中。

附录中列出了 @WebFluxTest 启用的自动配置。

默认情况下,Data JPA 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参阅Spring框架参考文档中的相关部分。如果这不是你想要的,可以禁用测试或整个类的事务管理,如下所示:
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class ExampleNonTransactionalTests {

}

Data JPA 测试还可以注入一个 TestEntityManager bean,它提供了一个专门为测试设计的标准 JPA EntityManager 的替代。如果要在 @DataJpaTest 实例之外使用TestEntityManager,还可以使用 @AutoConfigureTestEntityManager 注解。如果需要,还可以使用 JdbcTemplate。以下示例使用了@DataJpaTest注解:

    <!-- 内存嵌入式数据库 -->
    <dependency>
      <groupId>org.apache.derby</groupId>
      <artifactId>derby</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
/** @author YiFeiXi */
@DataJpaTest
public class DataJpaTests {
  @Autowired private TestEntityManager entityManager;
  @Autowired private UserRepository userRepository;

  @Test
  public void find() throws Exception {
    this.entityManager.persist(new UserInfo(1, "张", "三"));
    UserInfo user = this.userRepository.findByFirstName("张");
    assertThat(user.getLastName()).isEqualTo("三");
  }
}

内存嵌入式数据库通常可以很好地用于测试,因为它们速度快,不需要任何安装。但是,如果希望对真实数据库运行测试,则可以使用 @AutoConfigureTestDatabase 注解,如下例所示:

@DataJpaTest
@AutoConfigureTestDatabase(replace=Replace.NONE)
class ExampleRepositoryTests {

    // ...

}

自动配置 JDBC 测试

@JdbcTest 类似于 @DataJpaTest,但只用于只需要 DataSource 而不使用 Spring Data JDBC 的测试。默认情况下,它配置内存嵌入式数据库和 JdbcTemplate。常规@Component bean不会加载到ApplicationContext中。

@Slf4j
@JdbcTest
public class JdbcTests {
  @Autowired private JdbcTemplate jdbcTemplate;

  @Test
  void test() {
    jdbcTemplate.execute(
        "create table user_info(id int primary key, first_name varchar(20), last_name varchar(20))");
    jdbcTemplate.execute("insert into user_info(id, first_name, last_name) values(1,'张','三')");
    Map<String, Object> user = jdbcTemplate.queryForMap("select * from user_info where id = ?", 1);
    log.info("{} -> {}", user.get("first_name"), user.get("last_name"));
    assertThat(user.get("last_name")).isEqualTo("三");
  }
}

附录中列出了 @WebFluxTest 启用的自动配置。

默认情况下,JDBC测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参阅Spring框架参考文档中的相关部分。如果这不是您想要的,您可以为测试或整个类禁用事务管理,如下所示:
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class ExampleNonTransactionalTests {}

如果您希望在真实数据库上运行测试,可以使用 @AutoConfigureTestDatabase 注解,方法与对 DataJpaTest 的方法相同。

自动配置 Data JDBC 测试

@DataJdbcTest 类似于 @JdbcTest,但用于使用 Spring Data JDBC repositories 的测试。默认情况下,它配置内存嵌入式数据库、JdbcTemplate 和 Spring Data JDBC repositories。常规@Component bean不会加载到ApplicationContext中。

附录中列出了 @WebFluxTest 启用的自动配置。

默认情况下,Data JDBC 测试是事务性的,并在每个测试结束时回滚。如果这不是您想要的,您可以为测试或整个测试类禁用事务管理,如JDBC示例所示。

如果希望在真实数据库上运行测试,可以使用 @AutoConfigureTestDatabase 注解,方法与对DataJpaTest的方法相同。

自动配置 JOOQ 测试

自动配置 MongoDB 测试

自动配置 Neo4j 测试

自动配置 Redis 测试

可以使用 @DataRedisTest 来测试Redis应用程序。默认情况下,它扫描 @RedisHash 类并配置Spring Data Redis repositories。常规@Component bean不会加载到ApplicationContext中。(有关在Spring Boot中使用Redis的更多信息,请参阅本章前面的“Redis”。)

附录中列出了 @WebFluxTest 启用的自动配置。

以下示例展示了 @DataRedisTest 的使用

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
/** @author YiFeiXi */
@DataRedisTest
public class DataRedisTests {
  @Autowired StringRedisTemplate stringRedisTemplate;

  @Test
  void test() {
    stringRedisTemplate.opsForValue().set("sex", "girl");
  }

  @Test
  void valueHasSet() {
    assertThat(stringRedisTemplate.opsForValue().get("sex")).isEqualTo("girl");
  }
}

自动配置 LDAP 测试

自动配置 Rest Clients

可以使用 @RestClientTest 注解来测试 REST clients。默认情况下,它自动配置Jackson、GSON和Jsonb支持,配置 RestTemplateBuilder,并添加对 MockRestServiceServer 的支持。常规@Component bean不会加载到ApplicationContext中。

附录中列出了 @WebFluxTest 启用的自动配置。

应该使用 @RestClientTestvaluecomponents 属性指定要测试的特定bean,如下例所示:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
/** @author YiFeiXi */
@RestClientTest
public class RestClientTests {
  private MockRestServiceServer server;
  @Autowired private RestTemplateBuilder restTemplateBuilder;
  private TestService testService;

  @BeforeEach
  void before() {
    RestTemplate restTemplate = restTemplateBuilder.build();
    testService = new TestService(restTemplate);
    server = MockRestServiceServer.createServer(restTemplate);
  }

  @Test
  void test() {
    server.expect(requestTo("/greet")).andRespond(withSuccess("suc", MediaType.TEXT_PLAIN));
    assertThat(testService.restReq()).isEqualTo("suc");
  }
}

自动配置 Spring REST Docs 测试

可以使用 @AutoConfigureRestDocs 注解在 Mock MVC、REST Assured 或WebTestClient 的测试中使用 Spring Rest Docs。它消除了Spring REST Docs 对JUnit扩展的需求。

@AutoConfigureRestDocs 可用于覆盖默认输出目录(如果使用Maven,则为`
target/generated-snippets;如果使用Gradle,则为
build/generated-snippets`)。它还可以用于配置出现在任何文档化uri中的host、
scheme 和 port 。

    <dependency>
      <groupId>org.springframework.restdocs</groupId>
      <artifactId>spring-restdocs-mockmvc</artifactId>
      <scope>test</scope>
    </dependency>
/** @author YiFeiXi */
@WebMvcTest(HelloWorldController.class)
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class RestDocsTests {
  @Autowired private MockMvc mockMvc;

  @Test
  void hello() throws Exception {
    this.mockMvc
        .perform(get("").accept(MediaType.TEXT_PLAIN))
        .andExpect(status().isOk())
        .andDo(document("list-users"));
  }
}

用 Mock MVC 自动配置 Spring REST Docs

@AutoConfigureRestDocs 自定义 MockMvc bean 以使用 Spring REST Docs。可以使用 @Autowired 注入它,并在测试中使用它,就像使用Mock MVC和Spring REST Docs 时一样,如下例所示:

@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class UserDocumentationTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void listUsers() throws Exception {
        this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
                .andExpect(status().isOk())
                .andDo(document("list-users"));
    }
}

如果需要比 @AutoConfigureRestDocs 的属性更多地控制Spring REST Docs配置,可以使用 RestDocksMockMvcConfigurationCustomizer bean,如下例所示:

@TestConfiguration
static class CustomizationConfiguration
        implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}

如果想使用Spring REST Docs对参数化输出目录的支持,可以创建 RestDocumentationResultHandler bean。自动配置使用此结果处理程序调用 alwaysDo,从而使每个 MockMvc 调用自动生成默认代码段。以下示例显示了被定义的RestDocumentationResultHandler

@TestConfiguration(proxyBeanMethods = false)
static class ResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{method-name}");
    }

}

使用 WebTestClient 自动配置 Spring REST Docs

@AutoConfigureRestDocs 也可以与 WebTestClient 一起使用。可以使用 @Autowired 注入它,并在测试中使用它,就像使用 @WebFluxTest 和 Spring REST Docs 时一样,如下例所示:

@WebFluxTest
@AutoConfigureRestDocs
class UsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void listUsers() {
        this.webTestClient.get().uri("/").exchange().expectStatus().isOk().expectBody()
                .consumeWith(document("list-users"));
    }

}

如果需要比 @AutoConfigureRestDocs 的属性更多地控制Spring REST Docs配置,可以使用 RestDocsWebTestClientConfigurationCustomizer bean,如下例所示:

@TestConfiguration(proxyBeanMethods = false)
public static class CustomizationConfiguration implements RestDocsWebTestClientConfigurationCustomizer {

    @Override
    public void customize(WebTestClientRestDocumentationConfigurer configurer) {
        configurer.snippets().withEncoding("UTF-8");
    }

}

使用 REST Assured 自动配置 Spring REST Docs

@AutoConfigureRestDocs 使一个 RequestSpecificatioin bean(预配置为使用Spring REST Docs)可用于您的测试。您可以使用 @Autowired 注入它,并像使用 REST-Assured 和 Spring REST Docs 时一样在测试中使用它,如下例所示:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class UserDocumentationTests {

    @Test
    void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
        given(documentationSpec).filter(document("list-users")).when().port(port).get("/").then().assertThat()
                .statusCode(is(200));
    }

}

如果需要比 @AutoConfigureRestDocs 的属性更多地控制Spring REST Docs配置,那么可以使用 RestDocsRestAssuredConfigurationCustomizer bean,如下例所示:

@TestConfiguration(proxyBeanMethods = false)
public static class CustomizationConfiguration implements RestDocsRestAssuredConfigurationCustomizer {

    @Override
    public void customize(RestAssuredRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}

额外的自动配置和片段

每个片段提供一个或多个 @AutoConfigure… 注解,即定义应该作为片段一部分包含的自动配置。可以通过创建自定义 @AutoConfigure… 注解或向测试添加 @ImportAutoConfiguration 来添加其他自动配置,如下例所示:

@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class ExampleJdbcTests {}

请确保不要使用常规的@Import 注解来导入自动配置,因为它们是由Spring Boot以特定方式处理的。

用户配置和片段

如果以合理的方式构造代码,那么默认情况下,@SpringBootApplication 类将用作测试的配置。
因此,重要的是不要在应用程序的主类中添加特定于其功能特定区域的配置设置。
假设使用的是Spring Batch,并且依赖于它的自动配置。可以按如下方式定义 @SpringBootApplication

@SpringBootApplication
@EnableBatchProcessing
public class SampleApplication {
    //...
}

因为这个类是测试的源配置,所以任何片段测试实际上都会尝试启动Spring Batch,这绝对不是您想要做的。建议的方法是将特定于区域的配置移动到与应用程序处于同一级别的单独 @Configuration 类,如下例所示:

@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
public class BatchConfiguration {
    //...
}

根据应用程序的复杂性,您可以为您的自定义设置一个单独的 @Configuration 类,也可以为每个域区域设置一个类。后一种方法允许您在一个测试中启用它,如果需要,可以使用@Import 注解。

测试片段从扫描中排除 @Configuration 类。例如,对于 @WebMvcTest,以下配置不会在测试片段加载的应用程序上下文中包含给定的 WebMvcConfigurer bean:

@Configuration
public class WebConfiguration {
    @Bean
    public WebMvcConfigurer testConfigurer() {
        return new WebMvcConfigurer() {
            //...
        };
    }
}

但是,下面的配置将导致测试片段加载自定义 WebMvcConfiguer

@Component
public class TestWebMvcConfigurer implements WebMvcConfigurer {
    //...
}

另一个混乱的来源是类路径扫描。假设,当您以合理的方式构造代码时,您需要扫描另一个包。您的应用程序可能类似于以下代码:

@SpringBootApplication
@ComponentScan({"com.example.app", "org.acme.another"})
public class SampleApplication{
    //...
}

这样做有效地覆盖了默认的组件扫描指令,其副作用是扫描这两个包,而不考虑您选择的片段。例如,@DataJpaTest 似乎突然扫描应用程序的组件和用户配置。同样,将自定义指令移动到单独的类是解决此问题的好方法。
如果这不适合,可以在测试层次结构中的某个位置创建@SpringBootConfiguration,以便使用它。或者,可以为测试指定一个 source,这将禁用查找默认源的行为。

使用 Spock 测试 SpringBoot 应用

如果希望使用Spock测试一个Spring Boot 应用,那么应该在应用程序的构建中添加对Spock的 spock-spring 模块的依赖。spock-spring 将Spring的测试框架集成到 Spock 中。建议使用Spock 1.2或更高版本,以从Spock的Spring框架和SpringBoot集成的许多改进中获益。有关更多详细信息,请参阅Spock的Spring模块的文档

test-auto-configuration 附录

SpringBoot 测试注解附录

测试实用程序

当测试应用程序类打包为spring boot的一部分时,一些在测试应用程序通常是有用的。

ConfigFileApplicationContextInitializer

ConfigFileApplicationContextInitializer 是一个 ApplicationContextInitializer,可以将其应用于测试加载 Spring Boot application.properties 文件。当不需要@SpringBootTest提供的全套功能时,可以使用它,如下例所示:

@ContextConfiguration(classes = Config.class,
    initializers = ConfigFileApplicationContextInitializer.class)

单独使用 ConfigFileApplicationContextInitializer 不支持 @Value(${…}) 注入。它的唯一工作是确保 application.properties 文件加载到Spring的环境中。对于 @Value 支持,需要另外配置 PropertySourcesPlaceholderConfigurer 或使用 @SpringBootTest,后者会自动配置一个。

/** @author YiFeiXi */
@Configuration
public class UserConfig {

  @Bean
  public UserInfo defaultUserInfo() {
    return new UserInfo(1, "张", "三");
  }
}
/** @author YiFeiXi */
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
    classes = UserConfig.class,
    initializers = ConfigFileApplicationContextInitializer.class)
public class ConfigFileTests {
  @Autowired private UserInfo userInfo;

  @Test
  void test() {
    assertThat(userInfo.getFirstName()).isEqualTo("张");
    assertThat(userInfo.getLastName()).isEqualTo("三");
  }
}

TestPropertyValues

TestPropertyValues 允许快速将属性添加到 ConfigurableEnvironmentConfigurableApplicationContext。可以使用 key=value 字符串调用它,如下所示:

/** @author YiFeiXi */
@SpringBootTest
public class TestPropertyValueTests {
  @Autowired private ConfigurableEnvironment environment;

  @Test
  void test() {
    TestPropertyValues.of("org=spring", "name=boot").applyTo(environment);
    String org = environment.getProperty("org");
    String name = environment.getProperty("name");
    assertThat(org).isEqualTo("spring");
    assertThat(name).isEqualTo("boot");
  }
}

OutputCapture

OutputCapture 是一个JUnit扩展,可用于捕获 System.outSystem.err 输出。添加 @ExtendWith(OutputCaptureExtension.class) 并将 CapturedOutput 作为参数注入测试类构造函数或测试方法来使用,如下所示:

/**
 * 输出捕捉
 *
 * @author YiFeiXi
 */
@ExtendWith(OutputCaptureExtension.class)
public class OutputCaptureTests {

  @Test
  void test(CapturedOutput output) {
    System.out.print("hello world!");
    assertThat(output).isEqualTo("hello world!");
  }
}

TestRestTemplate

TestRestTemplate 是Spring的 RestTemplate 的一个方便的替代品,它在集成测试中非常有用。您可以得到一个普通模板或一个发送基本HTTP身份验证的模板(带有用户名和密码)。在这两种情况下,模板都以测试友好的方式运行,不会在服务器端错误上引发异常。

Spring Framework 5.0提供了一个新的WebTestClient,可用于WebFlux集成测试以及WebFlux和MVC端到端测试。它为断言提供了一个流畅的API,与 TestRestTemplate 不同。
建议使用ApacheHTTP客户端(4.3.2或更高版本),但不是强制的。如果在类路径中有,TestRestTemplate 将通过适当配置 client 来响应。如果您确实使用了Apache的HTTP客户端,则会启用一些附加的测试友好功能:

  • 不遵循重定向(因此您可以断言响应位置)。
  • Cookies被忽略(因此模板是无状态的)。

TestRestTemplate 可以在集成测试中直接实例化,如下例所示:

/** @author YiFeiXi */
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class RestTemplateTests {

  @Test
  void test() {
    TestRestTemplate template = new TestRestTemplate();
    String responseBody = template.getForObject("http://127.0.0.1:8080/", String.class);
    assertThat(responseBody).isEqualTo("Hello World!");
  }
}

或者,如果将 @SpringBootTest 注解与 WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT 一起使用,则可以插入完全配置的 TestRestTemplate 并开始使用它。如果需要,可以通过 RestTemplateBuilder bean应用其他定制。任何未指定 host 和 port 的URL都会自动连接到嵌入式服务器,如下例所示:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebClientTests {

    @Autowired
    private TestRestTemplate template;

    @Test
    void testRequest() {
        HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation()).hasHost("other.example.com");
    }

    @TestConfiguration(proxyBeanMethods = false)
    static class Config {

        @Bean
        RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                    .setReadTimeout(Duration.ofSeconds(1));
        }
    }
}

资料

官方文档
代码示例
公众号:逸飞兮(专注于 Java 领域知识的深入学习,从源码到原理,系统有序的学习)

逸飞兮


逸飞兮
40 声望4 粉丝