单元测试
依赖注入应该使你的代码比使用传统的J2EE/Java EE开发时更少地依赖于容器。构成你应用程序的POJO(Plain Old Java Object)应该能够在JUnit或TestNG测试中进行测试,这些对象是通过使用new操作符实例化的,而不需要Spring或其他任何容器。你可以使用模拟对象(结合其他有价值的测试技术)来独立地测试你的代码。如果你遵循Spring的架构建议,那么你所获得的代码结构的清晰分层和组件化将有助于更轻松地进行单元测试。例如,你可以通过模拟或伪造DAO(Data Access Object)或Repository接口来测试服务层对象,而在运行单元测试时无需访问持久化数据。
真正的单元测试通常运行得非常快,因为不需要设置任何运行时基础设施。将真正的单元测试作为开发方法论的一部分来强调,可以提升你的生产力。对于基于IoC的应用程序来说,你可能不需要这一部分的测试章节来帮助你编写有效的单元测试。然而,在某些单元测试场景下,Spring框架提供了模拟对象(mock objects)和测试支持类,这些内容将在本章中介绍。
Mock Objects
Spring包含了许多专门用于模拟(mocking)的包:
环境
org.springframework.mock.env 包包含了 Environment 和 PropertySource 抽象的模拟实现(参见 Bean Definition Profiles 以及 PropertySource Abstraction)。MockEnvironment 和 MockPropertySource 对于开发依赖于环境特定属性的代码的容器外测试非常有用。
Servlet API
org.springframework.mock.web 包包含了一整套 Servlet API 模拟对象,这些对象对于测试 Web 环境、控制器和过滤器非常有用。这些模拟对象是为与 Spring 的 Web MVC 框架一起使用而设计的,通常比动态模拟对象(如 EasyMock)更便于使用。
从Spring Framework 7.0开始,org.springframework.mock.web中的mock对象是基于Servlet 6.1 API的。
MockMvc基于模拟的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包含了许多可以帮助进行单元测试的类。这些类可以分为两类:
通用测试工具
org.springframework.test.util 包包含了几种通用工具,可用于单元测试和集成测试。
AopTestUtils 是一组与 AOP 相关的实用方法。你可以使用这些方法来获取隐藏在一个或多个 Spring 代理后面的目标对象的引用。例如,如果你使用 EasyMock 或 Mockito 等库将一个 Bean 配置为动态模拟对象(dynamic mock),而该模拟对象又被包装在 Spring 代理中,那么你可能需要直接访问该底层模拟对象以便对其进行预期配置和验证操作。关于 Spring 的核心 AOP 实用工具,请参阅 AopUtils 和 AopProxyUtils。
ReflectionTestUtils 是一组基于反射的实用方法。在测试场景中,当你需要更改常量的值、设置非public字段、调用非public的setter方法,或者在测试应用程序代码时调用非public的配置或生命周期回调方法时,可以使用这些方法。例如在以下用例中:
-
ORM框架(如JPA和Hibernate)允许直接访问
private或protected字段,而不是通过域实体中的publicsetter方法来设置属性。 -
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 结合 MockHttpServletRequest、MockHttpSession 等来自 Spring 的 Servlet API mocks 的工具。对于对您的 Spring MVC 和 REST Controller 类与 Spring MVC 的 WebApplicationContext 配置进行全面的集成测试,建议使用 MockMvc。