单元测试

依赖注入应该使您的代码对容器的依赖程度低于传统 J2EE / Java EE 开发中的依赖程度。构成应用程序的 POJO 应该在 JUnit 或 TestNG 测试中可测试,使用 new 运算符实例化对象,无需 Spring 或任何其他容器。您可以使用 模拟对象(结合其他有价值的测试技术)来隔离测试您的代码。如果您遵循 Spring 的架构建议,则由此产生的代码库的干净分层和组件化将促进更轻松的单元测试。例如,您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,而无需在运行单元测试时访问持久数据。

真正的单元测试通常运行速度非常快,因为没有运行时基础设施需要设置。将真正的单元测试作为开发方法的一部分可以提高您的生产力。您可能不需要本测试章节的这一部分来帮助您为基于 IoC 的应用程序编写有效的单元测试。但是,对于某些单元测试场景,Spring 框架提供了模拟对象和测试支持类,本章将对此进行介绍。

模拟对象

Spring 包含多个专门用于模拟的包

环境

org.springframework.mock.env 包包含 EnvironmentPropertySource 抽象的模拟实现(参见 Bean 定义配置文件PropertySource 抽象)。MockEnvironmentMockPropertySource 有助于为依赖于环境特定属性的代码开发容器外测试。

JNDI

org.springframework.mock.jndi 包包含 JNDI SPI 的部分实现,您可以使用它为测试套件或独立应用程序设置简单的 JNDI 环境。例如,如果 JDBC DataSource 实例在测试代码中绑定到与 Jakarta EE 容器中相同的 JNDI 名称,则可以在测试场景中无需修改即可重用应用程序代码和配置。

从 Spring Framework 5.2 开始,org.springframework.mock.jndi 包中的模拟 JNDI 支持正式弃用,取而代之的是来自第三方(例如 Simple-JNDI)的完整解决方案。

Servlet API

org.springframework.mock.web 包包含一组全面的 Servlet API 模拟对象,这些对象对于测试 Web 上下文、控制器和过滤器很有用。这些模拟对象针对 Spring 的 Web MVC 框架的使用,通常比动态模拟对象(例如 EasyMock)或其他 Servlet API 模拟对象(例如 MockObjects)更方便使用。

自 Spring Framework 6.0 起,org.springframework.mock.web 中的模拟对象基于 Servlet 6.0 API。

Spring MVC 测试框架基于模拟 Servlet API 对象,为 Spring MVC 提供了一个集成测试框架。参见 MockMvc

Spring Web Reactive

org.springframework.mock.http.server.reactive 包含 ServerHttpRequestServerHttpResponse 的模拟实现,用于 WebFlux 应用程序。org.springframework.mock.web.server 包含一个模拟 ServerWebExchange,它依赖于这些模拟请求和响应对象。

MockServerHttpRequestMockServerHttpResponse 都扩展自与服务器特定实现相同的抽象基类,并与它们共享行为。例如,模拟请求在创建后是不可变的,但可以使用 ServerHttpRequestmutate() 方法创建修改后的实例。

为了使模拟响应正确地实现写入契约并返回写入完成句柄(即 Mono<Void>),它默认使用带有 cache().then()Flux,它缓冲数据并使其在测试中可用于断言。应用程序可以设置自定义写入函数(例如,测试无限流)。

WebTestClient 基于模拟请求和响应,为测试 WebFlux 应用程序提供支持,而无需 HTTP 服务器。该客户端也可以用于与正在运行的服务器进行端到端测试。

单元测试支持类

Spring 包含许多可以帮助进行单元测试的类。它们分为两类

通用测试实用程序

org.springframework.test.util 包含几个用于单元测试和集成测试的通用实用程序。

AopTestUtils 是一个包含 AOP 相关实用程序方法的集合。可以使用这些方法获取隐藏在 Spring 代理后面的底层目标对象的引用。例如,如果使用 EasyMock 或 Mockito 等库将 bean 配置为动态模拟,并且模拟包装在 Spring 代理中,则可能需要直接访问底层模拟以对其配置期望并执行验证。对于 Spring 的核心 AOP 实用程序,请参见 AopUtilsAopProxyUtils

ReflectionTestUtils 是一个包含基于反射的实用程序方法的集合。可以使用这些方法在需要更改常量值、设置非 public 字段、调用非 public setter 方法或调用非 public 配置或生命周期回调方法的测试场景中,在测试应用程序代码时用于以下用例

  • ORM 框架(例如 JPA 和 Hibernate)允许对域实体中的属性使用 `private` 或 `protected` 字段访问,而不是使用 `public` setter 方法。

  • Spring 支持注解(例如 `@Autowired`、`@Inject` 和 `@Resource`),这些注解为 `private` 或 `protected` 字段、setter 方法和配置方法提供依赖注入。

  • 使用 `@PostConstruct` 和 `@PreDestroy` 等注解来定义生命周期回调方法。

TestSocketUtils 是一个简单的实用程序,用于在 `localhost` 上查找可用的 TCP 端口,以便在集成测试场景中使用。

TestSocketUtils 可用于在集成测试中启动在可用随机端口上运行的外部服务器。但是,这些实用程序不保证给定端口的后续可用性,因此不可靠。建议不要使用 `TestSocketUtils` 来查找服务器可用的本地端口,而是依靠服务器在随机的短暂端口上启动的能力,该端口由服务器选择或由操作系统分配。要与该服务器交互,您应该查询服务器当前使用的端口。

Spring MVC 测试实用程序

org.springframework.test.web 包含 ModelAndViewAssert,您可以将其与 JUnit、TestNG 或任何其他测试框架结合使用,用于处理 Spring MVC ModelAndView 对象的单元测试。

单元测试 Spring MVC 控制器
要将 Spring MVC Controller 类作为 POJO 进行单元测试,请将 ModelAndViewAssert 与 Spring 的 Servlet API 模拟 中的 MockHttpServletRequestMockHttpSession 等结合使用。要对 Spring MVC 和 REST Controller 类进行彻底的集成测试,并结合 Spring MVC 的 WebApplicationContext 配置,请使用 Spring MVC 测试框架