与传统的 Java EE 开发相比,依赖注入应该使你的代码对容器的依赖性降低。组成你的应用程序的 POJOs 应该可以在 JUnit 或 TestNG 测试中进行测试,通过使用 new 操作符来实例化对象,而不需要 Spring 或任何其他容器。你可以使用 模拟对象(结合其他有价值的测试技术)来孤立地测试你的代码。如果你遵循 Spring 的架构建议,由此产生的干净的分层和代码库的组件化有利于更容易的单元测试。例如,你可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,在运行单元测试时不需要访问持久化数据。
真正的单元测试通常运行得非常快,因为没有运行时的基础设施需要设置。强调真正的单元测试 是你开发方法的一部分,可以提高你的生产力。你可能不需要测试章节的这一部分来帮助你为基于 IoC 的应用程序编写有效的单元测试。然而,对于某些单元测试场景,Spring 框架提供了模拟对象和测试支持类,本章对此进行了描述。
:::tips 这个有点难用起来,文档中就是给了一点线索,没有任何的例子 :::
Mock Objects / 模拟对象
Spring 包括一些专门用于 mocking 的包:
- Environment
- JNDI
- Servlet API
- Spring Web Reactive
Environment
org.springframework.mock.env
包包含环境和 PropertySource 抽象的模拟实现(参见 Bean 定义配置文件 和 PropertySource 抽象)。MockEnvironment 和 MockPropertySource 对于开发依赖环境特定属性的代码的容器外测试非常有用。
比如下面的例子
package cn.mrcode.study.springdocsread.test;
import org.junit.jupiter.api.Test;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import java.util.Map;
/**
* @author mrcode
*/
public class DemoTest {
@Test
public void env() {
final MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.withProperty("a", "bbc");
final String a = mockEnvironment.getProperty("a");
// bbc
System.out.println(a);
// 获取系统属性
final Map<String, Object> systemProperties = mockEnvironment.getSystemProperties();
// 设置激活的环境
mockEnvironment.setActiveProfiles("dev");
}
@Test
public void prop() {
final MockPropertySource mockPropertySource = new MockPropertySource();
mockPropertySource.setProperty("a", "bbc");
final Object a = mockPropertySource.getProperty("a");
System.out.println(a);
}
}
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)允许
private
或protected
的字段访问,而不是域实体中属性的 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 测试框架。 :::