SpringBoot单元测试之常见框架和注解  第1张

Mock的概念

在软件开发中提及"mock",凡是理解为模仿对象。它能够用来对系统、组件或类停止隔离。在测试过程中,我们凡是存眷测试对象自己的功用和行为,而对测试对象涉及的一些依赖,仅仅存眷它们与测试对象之间的交互(好比能否挪用、何时挪用、挪用的参数、挪用的次数和挨次,以及返回的成果或发作的异常等),其实不存眷那些被依赖对象若何施行此次挪用的详细细节。因而,Mock 机造就是利用 Mock 对象替代实在的依赖对象,并模仿实在场景来开展测试工做。

利用 Mock 对象完成依赖关系测试的示企图如下所示:

SpringBoot单元测试之常见框架和注解  第2张

SPRingBootTest包导入的组件

好比 JUnit、JSON Path、AssertJ、Mockito、Hamcrest 等,那里我们有需要对那些组件停止展开申明。

JUnit:JUnit 是一款十分流行的基于 Java 语言的单位测试框架,在我们的课程中次要利用该框架做为根底的测试框架。JSON Path:类似于 XPath 在 XML 文档中的定位,JSON Path 表达式凡是用来检索途径或设置 JSON 文件中的数据。AssertJ:AssertJ 是一款强大的流式断言东西,它需要遵守 3A 核心原则,即 Arrange(初始化测试对象或筹办测试数据)——> Actor(挪用被测办法)——>Assert(施行断言)。Mockito:Mockito 是 Java 世界中一款流行的 Mock 测试框架,它次要利用简洁的 API 实现模仿操做。在施行集成测试时,我们将大量利用到那个框架。Hamcrest:Hamcrest 供给了一套婚配器(Matcher),此中每个婚配器的设想用于施行特定的比力操做。JSONassert:JSONassert 是一款专门针对 JSON 供给的断言框架。Spring Test & Spring Boot Test:为 Spring 和 Spring Boot 框架供给的测试东西。Spring单位测试编写

大致能够分为如下三类:

单位测试:一般面向办法,编写一般营业代码时,测试成本较大。涉及到的注解有@Test。切片测试:一般面向难于测试的鸿沟功用,介于单位测试和功用测试之间。涉及到的注解有@RunWith、@WebMvcTest等。功用测试:一般面向某个完好的营业功用,同时也能够利用切面测试中的mock才能,保举利用。涉及到的注解有@RunWith、@SpringBootTest等。初始化测试情况@SpringBootTest@RunWith(SpringRunner.class)复造代码@SpringBootTest注解

默认情况下,@SpringBootTest不会启动嵌入式的办事器。您能够利用 @SpringBootTest 的 webEnvironment 属性进一步完美测试的运行体例:

MOCK: 加载 WebApplicationContext 并供给一个 Mock 的 Servlet 情况,此时内置的 Servlet 容器并没有正式启动,能够共同@AutoConfigureMockMvc或@AutoConfigureWebTestClient连系利用。在大都场景下,一个实在的 Servlet 情况关于测试而言过于重量级,通过 MOCK 情况则能够缓解那种情况约束所带来的成本和挑战。RANDOM_PORT: 加载 EmbeddedWebApplicationContext 并供给一个实在的 Servlet 情况,然后利用一个随机端口启动内置容器。DEFINED_PORT: 那个设置装备摆设也是通过加载 EmbeddedWebApplicationContext 供给一个实在的 Servlet 情况,但利用的是默认端口,若是没有设置装备摆设端口就利用 8080。NONE: 加载 ApplicationContext 但其实不供给任何实在的 Servlet 情况利用号令行参数

若是您的应用法式需要arguments,您能够利用@SpringBootTest的args属性注入它们

@SpringBootTest(args = "--app.test=one")class ApplicationArgumentsExampleTests { @Test void applicationArgumentsPopulated(@Autowired ApplicationArguments args) { assertThat(args.getOptionNames()).containsOnly("app.test"); assertThat(args.getOptionValues("app.test")).containsOnly("one"); }}复造代码常见测试注解总结SpringBoot单元测试之常见框架和注解  第3张

主动设置装备摆设类型的注解(@AutoConfigure*)@AutoConfigureJsonTesters 主动设置装备摆设JsonTester@AutoConfigureTestEntityManager 主动设置装备摆设TestEntityManager@AutoConfigureMockRestServiceServer 主动设置装备摆设 MockRestServiceServer@AutoConfigureWebTestClient 主动设置装备摆设 WebTestClient@AutoConfigureMockMvc 主动设置装备摆设 MockMvc@AutoConfigureTestDatabase 主动设置装备摆设Test Database,能够利用内存数据库事务回滚@Transactional

零丁的@Transactional是回滚事务,在添加@Transactional的情况下若是要提交事务,只需要增加@Rollback(false);别的因为@Rollback能够用在办法上,所以一个测试类中,我们能够实现部门测试办法用@Rollback回滚事务,部门测试办法用@Rollback(false)来提交事务。

TestRestTemplate、WebTestClient 和 MockMvc 有什么区别?

固然那三位候选者都办事于类似的目的:挪用我们的 HTTP 端点并验证响应

MockMvc : 与模仿 servlet 情况交互的 Fluent API,有撑持验证办事器端衬着视图端点的模子或视图名称的 API。

利用MockMvc,我们不需要启动我们的嵌入式 servlet 容器(例如Tomcat)。因而我们不占用任何端口。我们利用该MockMvc实例与那个模仿情况交互,而不启动实正的 HTTP 通信。

WebTestClient : 最后是用于挪用和验证 Spring WebFlux 端点的测试东西。然而,我们也能够利用它为正在运行的servlet容器或MockMvc编写测试。

TestRestTemplate : 通过HTTP测试和验证正在运行的servlet容器的控造器端点,API不太流利。

SpringBoot单元测试之常见框架和注解  第4张

MockMvc东西类

MockMvc 类供给的根底办法分为以下 6 种,下面逐个对应来看下。

PErform:施行一个 RequestBuilder 恳求,会主动施行 SpringMVC 流程并映射到响应的 Controller 停止处置。get/post/put/delete:声明发送一个 HTTP 恳求的体例,按照 URI 模板和 URI 变量值得到一个 HTTP 恳求,撑持 GET、POST、PUT、DELETE 等 HTTP 办法。param:添加恳求参数,发送 JSON 数据时将不克不及利用那种体例,而应该接纳 @ResponseBody 注解。andExpect:添加 ResultMatcher 验证规则,通过对返回的数据停止判断来验证 Controller 施行成果能否准确。andDo:添加 ResultHandler 成果处置器,好比调试时打印成果到控造台。andReturn:最初返回响应的 MvcResult,然后施行自定义验证或做异步处置。测试案例 @Test void pageMessageCenterByUserId(@Autowired MockMvc mvc) throws Exception { MvcResult mvcResult = mvc.perform(get("xxx") // 恳求数据类型 .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) // 返回数据类型 .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) // session会话对象 .session(session) // URL传参 .param("key", "value") // body传参 .content(json)) // 验证参数 .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) // 打印恳求和响应体 .andDo(MockMvcResultHandlers.print()); // 打印响应body System.out.println(mvcResult.getResponse().getContentAsString()); }复造代码测试下载文件

利用mockMvc测试下载文件时,需要留意controller办法的返回值需要为void,不然会报HttpMessageNotWritableException的异常错误

@Test@WithUserDetails("admin")@DisplayName("测试下载excel文件")void downExcel() throws Exception { mockMvc.perform(get("/system/operate/export/excel") .accept(MediaType.APPLICATION_OCTET_STREAM) .param("startTime", "2022-11-22 10:51:25") .param("endTime", "2022-11-23 10:51:25")) .andExpect(status().isOk()) .andDo((result) -> { String contentDisposition = result.getResponse().getHeader("Content-Disposition"); String fileName = URLDecoder.decode(contentDisposition.split("=")[1]); ByteArrayInputStream inputStream = new ByteArrayInputStream(result.getResponse().getContentAsByteArray()); String basePath = System.getProperty("user.dir"); // 保留为文件 File file = new File(basePath + "/" + fileName); FileUtil.del(file); FileOutputStream outputStream = new FileOutputStream(file); StreamUtils.copy(inputStream, outputStream); outputStream.close(); inputStream.close(); });}复造代码SpringSecurity 单位测试

Spring Security 也供给了专门用于测试平安性功用的 spring-security-test 组件,如下所示:

<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope></dependency>复造代码测试用户/认证

利用@WithMockUser、@WithUserDetails、@WithAnonymousUser等注解

@WithAnonymousUser是用来模仿一种特殊的用户,也被叫做匿名用户。若是有测试匿名用户的需要,能够间接利用该注解。

@WithMockUser注解能够帮我们在Spring Security平安上下文中模仿一个用户。

固然 @WithMockUser是一种十分便利的体例,但可能并不是在所有情况下都凑效。有时候你魔改了一些工具使得平安上下文的验证机造发作了改动,好比你定造了UserDetails,那一类注解就欠好用了。但是通过UserDetailsService 加载的用户往往仍是可靠的。

@WithUserDetails就派上了用场,它会按照传入的用户名挪用UserDetailsService 的loadUserByUsername办法查找用户并加载到平安上下文中

测试 CSRF

下述 csrf() 办法的感化就是在恳求中添加 CSRF Token

@Testpublic void testHelloUsingPOSTWithCSRF() throws Exception { mvc.perform(post("/hello").with(csrf())) .andExpect(status().isOk());}复造代码测试CORS

我们通过 MockMvc 倡议恳求,然后对响应的动静头停止验证即可,测试用例如下所示:

@SpringBootTest@AutoConfigureMockMvcpublic class MainTests { @Autowired private MockMvc mvc; @Test public void testCORSForTestEndpoint() throws Exception { mvc.perform(options("/hello") .header("Access-Control-Request-Method", "POST") .header("Origin", "http://www.test.com") ) .andExpect(header().exists("Access-Control-Allow-Origin")) .andExpect(header().string("Access-Control-Allow-Origin", "*")) .andExpect(header().exists("Access-Control-Allow-Methods")) .andExpect(header().string("Access-Control-Allow-Methods", "POST")) .andExpect(status().isOk()); }}复造代码

能够看到,针对 CORS 设置装备摆设,我们别离获取了响应成果的"Access-Control-Allow-Origin"和"Access-Control-Allow-Methods"动静头并停止了验证。

SpringBoot加载测试公用属性

用args添加临时号令行参数

@SpringBootTest(args = {"--test.prop=test"})复造代码

激活指定设置装备摆设文件

@ActiveProfiles("pro")复造代码

加载其他设置装备摆设文件

@TestPropertySource(locations = "classpath:config-test.properties")复造代码常见问题Junit版本问题

若是利用的是JUnit 4,需要添加@RunWith(SpringRunner.class)到测试中,不然会报错。若是您利用的是JUnit 5,则无需添加,因为@SpringBootTest中已经添加了@ExtendWith(SpringExtension.class),测试类中不需要在写@Runwith的时候,能够在pom中排除junit4的依赖。

断言

JUnit5 断言利用org.junit.jupiter.api.Assertions的静态办法。 除此之外还能够利用 AssertJ(org.assertj.core.api.Assertions的assertThat办法)。

单位测试中@Transactional不回滚的问题

docs.spring.io/spring-boot…

If YOUr test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.

若是您的测试是@Transactional,默认情况下,它会在每个测试办法完毕时回滚事务。然而,利用RANDOM_PORT或DEFINED_PORT的那种供给了一个实正的servlet情况的情况下,HTTP客户端和办事器在零丁的线程中运行,因而会在零丁的事务中运行。那种情况下,办事器上倡议的任何事务都不会回滚。

you should use caution if Spring-managed or application-managed transactions are configured with any propagation type other than REQUIRED or SUPPORTS (see the discussion on transaction propagation for details).

若是Spring办理的或应用办理的事务被设置装备摆设为REQUIRED或SUPPORTS以外的任何传布类型,你应该隆重行事,因为它们都需要遵照事务的传布体例,也会呈现事务不会滚的问题,好比你用了REQUIRED_NEW的话就跟单位测试中的事务不在一个事务中了,所以无法回滚。

Maven打包时单位测试不运行

做者:Linn链接:https://juejin.cn/post/7202639428131553338