乐趣区

关于springboot:使用-Spring-Boot-和-SpringBootTest-进行测试

【注】本文译自:Testing with Spring Boot and @SpringBootTest – Reflectoring

应用@SpringBootTest 注解,Spring Boot 提供了一种不便的办法来启动要在测试中应用的应用程序上下文。在本教程中,咱们将探讨何时应用 @SpringBootTest 以及何时更好地应用其余工具进行测试。咱们还将钻研自定义应用程序上下文的不同办法以及如何缩小测试运行工夫。

 代码示例

本文附有 GitHub 上的工作代码示例。

“应用 Spring Boot 进行测试”系列

本教程是系列的一部分:

  1. 应用 Spring Boot 进行单元测试
  2. 应用 Spring Boot 和 @WebMvcTest 测试 MVC Web Controller
  3. 应用 Spring Boot 和 @DataJpaTest 测试 JPA 查问
  4. 应用 Spring Boot 和 @SpringBootTest 进行测试

集成测试与单元测试

在开始应用 Spring Boot 进行集成测试之前,让咱们定义集成测试与单元测试的区别。
单元测试涵盖单个“单元”,其中一个单元通常是单个类,但也能够是组合测试的一组内聚类。
集成测试能够是以下任何一项:

  • 涵盖多个“单元”的测试。它测试两个或多个内聚类集群之间的交互。
  • 笼罩多个层的测试。这实际上是第一种状况的特化,例如可能涵盖业务服务和长久层之间的交互。
  • 涵盖整个应用程序门路的测试。在这些测试中,咱们向应用程序发送申请并查看它是否正确响应并依据咱们的预期更改了数据库状态。

Spring Boot 提供了 @SpringBootTest 注解,咱们能够应用它来创立一个应用程序上下文,其中蕴含咱们对上述所有测试类型所需的所有对象。然而请留神,适度应用 @SpringBootTest 可能会导致测试套件运行工夫十分长。
因而,对于涵盖多个单元的简略测试,咱们应该创立简略的测试,与单元测试十分类似,在单元测试中,咱们手动创立测试所需的对象图并模仿其余部分。这样,Spring 不会在每次测试开始时启动整个应用程序上下文。

测试切片

咱们能够将咱们的 Spring Boot 应用程序作为一个整体来测试、一个单元一个单元地测试、也能够一层一层地测试。应用 Spring Boot 的测试切片注解,咱们能够别离测试每一层。
在咱们具体钻研 @SpringBootTest 注解之前,让咱们摸索一下测试切片注解,以查看 @SpringBootTest 是否真的是您想要的。
@SpringBootTest 注解加载残缺的 Spring 应用程序上下文。相比之下,测试切片正文仅加载测试特定层所需的 bean。正因为如此,咱们能够防止不必要的模仿和副作用。

@WebMvcTest

咱们的 Web 控制器承当许多职责,例如侦听 HTTP 申请、验证输出、调用业务逻辑、序列化输入以及将异样转换为正确的响应。咱们应该编写测试来验证所有这些性能。
@WebMvcTest 测试切片正文将应用刚好足够的组件和配置来设置咱们的应用程序上下文,以测试咱们的 Web 控制器层。例如,它将设置咱们的 @Controller@ControllerAdvice、一个 MockMvc bean 和其余一些主动配置。
要浏览无关 @WebMvcTest 的更多信息并理解咱们如何验证每个职责,请浏览我对于应用 Spring Boot 和 @WebMvcTest 测试 MVC Web 控制器的文章。

@WebFluxTest

@WebFluxTest 用于测试 WebFlux 控制器。@WebFluxTest 的工作形式相似于 @WebMvcTest 正文,不同之处在于它不是 Web MVC 组件和配置,而是启动 WebFlux 组件和配置。其中一个 bean 是 WebTestClient,咱们能够应用它来测试咱们的 WebFlux 端点。

@DataJpaTest

就像 @WebMvcTest 容许咱们测试咱们的 web 层一样,@DataJpaTest 用于测试长久层。
它配置咱们的实体、存储库并设置嵌入式数据库。当初,这所有都很好,然而,测试咱们的长久层意味着什么?咱们到底在测试什么?如果查问,那么什么样的查问?要找出所有这些问题的答案,请浏览我对于应用 Spring Boot 和 @DataJpaTest 测试 JPA 查问的文章。

@DataJdbcTest

Spring Data JDBC 是 Spring Data 系列的另一个成员。如果咱们正在应用这个我的项目并且想要测试长久层,那么咱们能够应用 @DataJdbcTest 注解。@DataJdbcTest 会主动为咱们配置在咱们的我的项目中定义的嵌入式测试数据库和 JDBC 存储库。
另一个相似的我的项目是 Spring JDBC,它为咱们提供了 JdbcTemplate 对象来执行间接查问。@JdbcTest 注解主动配置测试咱们的 JDBC 查问所需的 DataSource 对象。依赖
本文中的代码示例只须要依赖 Spring Boot 的 test starter 和 JUnit Jupiter:

dependencies {testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
}

应用 @SpringBootTest 创立 ApplicationContext

@SpringBootTest 在默认状况下开始在测试类的以后包中搜寻,而后在包构造中向上搜寻,寻找用 @SpringBootConfiguration 注解的类,而后从中读取配置以创立应用程序上下文。这个类通常是咱们的次要应用程序类,因为 @SpringBootApplication 注解包含 @SpringBootConfiguration 注解。而后,它会创立一个与在生产环境中启动的应用程序上下文十分类似的应用程序上下文。
咱们能够通过许多不同的形式自定义此应用程序上下文,如下一节所述。
因为咱们有一个残缺的应用程序上下文,包含 web 控制器、Spring 数据存储库和数据源,@SpringBootTest 对于贯通应用程序所有层的集成测试十分不便:

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private UserRepository userRepository;

  @Test
  void registrationWorksThroughAllLayers() throws Exception {UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");

    mockMvc.perform(post("/forums/{forumId}/register", 42L)
            .contentType("application/json")
            .param("sendWelcomeMail", "true")
            .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isOk());

    UserEntity userEntity = userRepository.findByName("Zaphod");
    assertThat(userEntity.getEmail()).isEqualTo("zaphod@galaxy.net");
  }
}

@ExtendWith
本教程中的代码示例应用 @ExtendWith 注解通知 JUnit 5 启用 Spring 反对。从 Spring Boot 2.1 开始,咱们不再须要加载 SpringExtension,因为它作为元正文蕴含在 Spring Boot 测试正文中,例如 @DataJpaTest@WebMvcTest@SpringBootTest

在这里,咱们另外应用 @AutoConfigureMockMvc 将 MockMvc 实例增加到应用程序上下文中。
咱们应用这个 MockMvc 对象向咱们的应用程序执行 POST 申请并验证它是否按预期响应。
而后,咱们应用应用程序上下文中的 UserRepository 来验证申请是否导致数据库状态产生预期的变动。

自定义应用程序上下文

咱们能够有很多种办法来自定义 @SpringBootTest 创立的应用程序上下文。让咱们看看咱们有哪些抉择。

自定义利用上下文时的注意事项
应用程序上下文的每个自定义都是使其与在生产设置中启动的“实在”应用程序上下文不同的另一件事。因而,为了使咱们的测试尽可能靠近生产,咱们应该只定制让测试运行真正须要的货色!

增加主动配置

在下面,咱们曾经看到了主动配置的作用:

@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {...}

还有很多其余可用的主动配置,每个都能够将其余 bean 增加到应用程序上下文中。以下是文档中其余一些有用的内容:

  • @AutoConfigureWebTestClient:将 WebTestClient 增加到测试应用程序上下文。它容许咱们测试服务器端点。
  • @AutoConfigureTestDatabase:这容许咱们针对实在数据库而不是嵌入式数据库运行测试。
  • @RestClientTest:当咱们想要测试咱们的 RestTemplate 时它会派上用场。它主动配置所需的组件以及一个 MockRestServiceServer 对象,该对象帮忙咱们模仿来自 RestTemplate 调用的申请的响应。
  • @JsonTest:主动配置 JSON 映射器和类,例如 JacksonTesterGsonTester。应用这些咱们能够验证咱们的 JSON 序列化 / 反序列化是否失常工作。

设置自定义配置属性

通常,在测试中须要将一些配置属性设置为与生产设置中的值不同的值:

@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {@Value("${foo}")
  String foo;

  @Test
  void test(){assertThat(foo).isEqualTo("bar");
  }
}

如果属性 foo 存在于默认设置中,它将被此测试的值 bar 笼罩。

应用 @ActiveProfiles 内部化属性

如果咱们的许多测试须要雷同的属性集,咱们能够创立一个配置文件 application-<profile>.propertieapplication-<profile>.yml 并通过激活某个配置文件从该文件加载属性:

# application-test.yml
foo: bar
@SpringBootTest
@ActiveProfiles("test")
class SpringBootProfileTest {@Value("${foo}")
  String foo;

  @Test
  void test(){assertThat(foo).isEqualTo("bar");
  }
}

应用 @TestPropertySource 设置自定义属性

另一种定制整个属性集的办法是应用 @TestPropertySource 正文:

# src/test/resources/foo.properties
foo=bar
@SpringBootTest
@TestPropertySource(locations = "/foo.properties")
class SpringBootPropertySourceTest {@Value("${foo}")
  String foo;

  @Test
  void test(){assertThat(foo).isEqualTo("bar");
  }
}

foo.properties 文件中的所有属性都加载到应用程序上下文中。@TestPropertySource 还能够 配置更多。

应用 @MockBean 注入模仿

如果咱们只想测试应用程序的某个局部而不是从传入申请到数据库的整个门路,咱们能够应用 @MockBean 替换应用程序上下文中的某些 bean:

@SpringBootTest
class MockBeanTest {

  @MockBean
  private UserRepository userRepository;

  @Autowired
  private RegisterUseCase registerUseCase;

  @Test
  void testRegister(){
    // given
    User user = new User("Zaphod", "zaphod@galaxy.net");
    boolean sendWelcomeMail = true;
    given(userRepository.save(any(UserEntity.class))).willReturn(userEntity(1L));

    // when
    Long userId = registerUseCase.registerUser(user, sendWelcomeMail);

    // then
    assertThat(userId).isEqualTo(1L);
  }
 
}

在这种状况下,咱们用模仿替换了 UserRepository bean。应用 Mockitogiven 办法,咱们指定了此模仿的预期行为,以测试应用此存储库的类。
您能够在我对于模仿的文章中浏览无关 @MockBean 注解的更多信息。

应用 @Import 增加 Bean

如果某些 bean 未蕴含在默认应用程序上下文中,但咱们在测试中须要它们,咱们能够应用 @Import 注解导入它们:

package other.namespace;

@Component
public class Foo {
}

@SpringBootTest
@Import(other.namespace.Foo.class)
class SpringBootImportTest {

  @Autowired
  Foo foo;

  @Test
  void test() {assertThat(foo).isNotNull();}
}

默认状况下,Spring Boot 应用程序蕴含它在其包和子包中找到的所有组件,因而通常只有在咱们想要蕴含其余包中的 bean 时才须要这样做。

应用 @TestConfiguration 笼罩 Bean

应用 @TestConfiguration,咱们不仅能够蕴含测试所需的其余 bean,还能够笼罩应用程序中曾经定义的 bean。在咱们对于应用 @TestConfiguration 进行测试的文章中浏览更多相干信息。

创立自定义 @SpringBootApplication

咱们甚至能够创立一个残缺的自定义 Spring Boot 应用程序来启动测试。如果这个应用程序类与真正的应用程序类在同一个包中,然而在测试源而不是生产源中,@SpringBootTest 会在理论应用程序类之前找到它,并从这个应用程序加载应用程序上下文。
或者,咱们能够通知 Spring Boot 应用哪个应用程序类来创立应用程序上下文:

@SpringBootTest(classes = CustomApplication.class)
class CustomApplicationTest {}

然而,在执行此操作时,咱们正在测试可能与生产环境齐全不同的应用程序上下文 ,因而仅当无奈在测试环境中启动生产应用程序时,这才应该是最初的伎俩。然而,通常有更好的办法,例如使实在的应用程序上下文可配置以排除不会在测试环境中启动的 bean。让咱们看一个例子。
假如咱们在应用程序类上应用 @EnableScheduling 注解。每次启动应用程序上下文时(即便在测试中),所有 @Scheduled 作业都将启动,并且可能与咱们的测试抵触。咱们通常不心愿作业在测试中运行,因而咱们能够创立第二个没有 @EnabledScheduling 正文的应用程序类,并在测试中应用它。然而,更好的解决方案是创立一个能够应用属性切换的配置类:

@Configuration
@EnableScheduling
@ConditionalOnProperty(
        name = "io.reflectoring.scheduling.enabled",
        havingValue = "true",
        matchIfMissing = true)
public class SchedulingConfiguration {}

咱们已将 @EnableScheduling 注解从咱们的应用程序类移到这个非凡的配置类。将属性 io.reflectoring.scheduling.enabled 设置为 false 将导致此类不会作为应用程序上下文的一部分加载:

@SpringBootTest(properties = "io.reflectoring.scheduling.enabled=false")
class SchedulingTest {@Autowired(required = false)
  private SchedulingConfiguration schedulingConfiguration;

  @Test
  void test() {assertThat(schedulingConfiguration).isNull();}
}

咱们当初曾经胜利地停用了测试中的预约作业。属性 io.reflectoring.scheduling.enabled 能够通过上述任何形式指定。

为什么我的集成测试这么慢?

蕴含大量 @SpringBootTest 正文测试的代码库可能须要相当长的工夫能力运行。Spring 的测试反对 足够智能,只创立一次利用上下文并在后续测试中重复使用,然而如果不同的测试须要不同的利用上下文,它依然会为每个测试创立一个独自的上下文,这须要一些工夫来实现每个测试。
下面形容的所有自定义选项都会导致 Spring 创立一个新的应用程序上下文 。因而,咱们可能心愿创立一个配置并将其用于所有测试,以便能够重用应用程序上下文。
如果您对测试破费在设置和 Spring 应用程序上下文上的工夫感兴趣,您可能须要查看 JUnit Insights,它能够蕴含在 Gradle 或 Maven 构建中,以生成对于 JUnit 5 如何破费工夫的很好的报告。

论断

@SpringBootTest 是一种为测试设置应用程序上下文的十分不便的办法,它十分靠近咱们将在生产中应用的上下文。有很多选项能够自定义此应用程序上下文,但应审慎应用它们,因为咱们心愿咱们的测试尽可能靠近生产运行。
如果咱们想在整个应用程序中进行测试,@SpringBootTest 会带来最大的价值。为了仅测试应用程序的某些切片或层,咱们还有其余选项可用。
本文中应用的示例代码可在 github 上找到。

退出移动版