与传统的 Java EE 开发相比,依赖注入应该使你的代码对容器的依赖性降低。组成你的应用程序的 POJOs 应该可以在 JUnit 或 TestNG 测试中进行测试,通过使用 new 操作符来实例化对象,而不需要 Spring 或任何其他容器。你可以使用 模拟对象(结合其他有价值的测试技术)来孤立地测试你的代码。如果你遵循 Spring 的架构建议,由此产生的干净的分层和代码库的组件化有利于更容易的单元测试。例如,你可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,在运行单元测试时不需要访问持久化数据。

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

:::tips 这个有点难用起来,文档中就是给了一点线索,没有任何的例子 :::

Mock Objects / 模拟对象

Spring 包括一些专门用于 mocking 的包:

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

比如下面的例子

  1. package cn.mrcode.study.springdocsread.test;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.mock.env.MockEnvironment;
  4. import org.springframework.mock.env.MockPropertySource;
  5. import java.util.Map;
  6. /**
  7. * @author mrcode
  8. */
  9. public class DemoTest {
  10. @Test
  11. public void env() {
  12. final MockEnvironment mockEnvironment = new MockEnvironment();
  13. mockEnvironment.withProperty("a", "bbc");
  14. final String a = mockEnvironment.getProperty("a");
  15. // bbc
  16. System.out.println(a);
  17. // 获取系统属性
  18. final Map<String, Object> systemProperties = mockEnvironment.getSystemProperties();
  19. // 设置激活的环境
  20. mockEnvironment.setActiveProfiles("dev");
  21. }
  22. @Test
  23. public void prop() {
  24. final MockPropertySource mockPropertySource = new MockPropertySource();
  25. mockPropertySource.setProperty("a", "bbc");
  26. final Object a = mockPropertySource.getProperty("a");
  27. System.out.println(a);
  28. }
  29. }

JNDI

org.springframework.mock.jndi包包含 JNDI SPI 的部分实现,你可以用它来为测试套件或独立的应用程序建立一个简单的 JNDI 环境。例如,如果 JDBC 数据源实例在测试代码中被绑定到与 Java EE 容器中相同的 JNDI 名称上,你就可以在测试场景中重复使用应用程序代码和配置,而无需修改。

:::tips org.springframework.mock.jndi 包中的模拟 JNDI 支持从 Spring Framework 5.2 开始正式废弃,转而采用第三方的完整解决方案,如Simple-JNDI。 :::

Servlet API

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

:::info 从 Spring Framework 5.0 开始,org.springframework.mock.web 中的 mock 对象是基于 Servlet 4.0 的 API。 :::

Spring MVC 测试框架建立在模拟 Servlet API 对象的基础上,为 Spring MVC 提供一个集成测试框架。参见 MockMvc

Spring Web Reactive

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

MockServerHttpRequest 和 MockServerHttpResponse 都是从相同的抽象基类中延伸出来的服务器专用实现,并与它们共享行为。例如,一个模拟请求一旦被创建就是不可改变的,但是你可以使用 ServerHttpRequest 的 mutate()方法来创建一个修改的实例。

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

WebTestClient 建立在模拟请求和响应的基础上,为测试没有 HTTP 服务器的 WebFlux 应用程序提供支持。该客户端也可用于与运行中的服务器进行端到端测试。

单元测试支持类

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

  • 通用测试工具类
  • Spring MVC 测试工具类

通用测试工具类

org.springframework.test.util 包包含几个通用的工具,用于单元和集成测试。

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

  • ORM 框架(如 JPA 和 Hibernate)允许 privateprotected 的字段访问,而不是域实体中属性的 public setter 方法。
  • Spring 支持注解(如 @Autowired、@Inject 和 @Resource),为 public 或 protected 的字段、setter 方法和配置方法提供依赖注入。
  • 使用注解,如 @PostConstruct 和 @PreDestroy,用于生命周期的回调方法。

AopTestUtils 是 AOP 相关实用方法的集合。你可以使用这些方法来获取隐藏在一个或多个 Spring 代理背后的底层目标对象的引用。例如,如果你通过使用 EasyMock 或 Mockito 等库将一个 bean 配置为动态模拟,并且该模拟被包裹在 Spring 代理中,你可能需要直接访问底层模拟,以便对其配置期望并执行验证。关于 Spring 的核心 AOP 工具,请参阅 AopUtils AopProxyUtils

Spring MVC 测试工具类

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

:::tips 单元测试 Spring MVC Controller

要将 Spring MVC 控制器类作为 POJO 进行单元测试,可以使用 ModelAndViewAssert 与 MockHttpServletRequest、MockHttpSession 等来自 Spring 的 Servlet API mocks 相结合。对于 Spring MVC 和 REST 控制器类与 Spring MVC 的 WebApplicationContext 配置的彻底集成测试,请使用 Spring MVC 测试框架。 :::