前言
测试在 SpringBoot 1.4 版本之后进行了改进,Testing improvements in Spring Boot 1.4。由于 Spring Boot 1.4 之前的测试方式均属于集成测试。一个单纯的单元测试不应该创建和加载 Spring 的上下文。在 1.4 版本之前想要使用单元测试测试一个带有@Autowired
注解的外部 services 的 controller 而不用去加载Spring
上下文,是不可能的。
简介
Spring Boot 1.4 解决的另外一个问题是,可以测试一段代码。不用启动服务器。并且不用启动整个Spring
上下文,Spring Boot 1.4 通过新的Test Slicing的特性 就可以完成,这个特性被设计成可以至启动一小片的Spring
上下文。这时的测试单个的代码片段更加容易了。你可以这样去测试你的应用中的特定代码片段
-
MVC 片段: 通过
@WebMvcTest
注解测试Controller
代码 -
JPA 片段: 通过
@DataJpaTest
注解测试Spring Data JPA repository
代码 -
JSON 片段: 通过
@JsonTest
注解JSON
序列化代码
解决的问题;大型的应用要测试的话,如果它启动Spring上下文,会很耗费时间
SpringBoot 1.4-
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
}
Or
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
public class ApplicationTests {
}
SpringBoot 1.4+
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
public class ApplicationTests {
}
注解
-
@RunWith(SpringRunner.class)
告诉Spring运行使用的JUnit测试支持。SpringRunner
是SpringJUnit4ClassRunner
的新名字,这个名字只是让名字看起来简单些。 -
@SpringBootTest
意思是“带有Spring Boot支持的引导程序”(例如,加载应用程序、属性,为我们提供Spring Boot的所有精华部分)。-
webEnvironment属性
允许为测试配置特定的“网络环境”。-
MOCK:
提供一个Mock的Servlet环境,内置的Servlet容器并没有真实的启动,主要搭配使用@AutoConfigureMockMvc -
RANDOM_PORT:
提供一个真实的Servlet环境,也就是说会启动内置容器,然后使用的是随机端口 -
DEFINED_PORT:
这个配置也是提供一个真实的Servlet环境,使用的默认的端口,如果没有配置就是8080 -
NONE:
这是个神奇的配置,跟Mock一样也不提供真实的Servlet环境。
-
-
classes
如果想要加载一个特定的配置,可以用@SpringBootTes
t的classes
属性。在这个实例中,省略classes
就意味着测试要首次尝试从任意一个inner-classes
中加载@configuration
,如果这个尝试失败了,它会在你主要的@SpringBootApplicationclass
中进行搜索。
-
实例
单例测试
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
@AutoConfigureWebMvc
public class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void index() throws Exception {
this.mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
// .andExpect(view().name("index"))
.andExpect(content().string(Matchers.containsString("Hello World")))
.andDo(print());
}
}
- 与
@SpringBootTest
注解不同@WebMvcTest
注解会把自动配置给禁用掉。-
@WebMvcTest
只会将 Spring MVC 的基础架构自动配置,并且仅对使用@Controller,@ControllerAdvice,@JsonComponent
注解的bean
,以及Filter
、WebMvcConfigurer
和HandlerMethodArgumentResolver
类型的bean
进行扫描。此时@Component、@Service 或 @Repository
注解的bean
将不会被扫描到
-
集成测试
@RunWith(SpringRunner.class)
//@WebMvcTest(controllers = HelloController.class)
//@AutoConfigureWebMvc
public class HelloControllerTest extends C1ApplicationTests {
// @Autowired
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}
@Test
public void index() throws Exception {
this.mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
// .andExpect(view().name("index"))
.andExpect(content().string(Matchers.containsString("Hello World")))
.andDo(print());
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = C1Application.class)
public class C1ApplicationTests {
}
- 如果想要加载所有的应用配置并且使用 MockMVC,就应该使用
@SpringBootTest
注解并且加上@AutoConfigureMockMvc
注解,而不是使用@WebMvcTest
注解。 - MockMvc 通过模拟 Spring MVC 来测试 MVC 网页应用。可以向一个
controller
发送模拟的HTTP
请求,这样不再需要启动应用服务器。可以通过 MockMvcBuilders 来获取 MockMvc 的实例。-
(推荐)standaloneSetup():注册一个或多个
@Controller
实例,并且允许通过编程去配置 Spring MVC 的基础架构 从而来构造一个 MockMvc 的实例。 这跟普通的单元测试很相似,同时也使得一次仅关注一个controller
的测试成为可能。 - webAppContextSetup(): 使用完全被初始化(并且刷新过)了的 WebApplicationContext 来构建一个MockMvc实例。这样使Spring可以加载你的控制层以及它们的所有依赖,从而进行一个完整的集成测试。
-
(推荐)standaloneSetup():注册一个或多个
方法解析:
-
perform:执行一个
RequestBuilder
请求,会自动执行 SpringMVC 的流程并映射到相应的控制器执行处理; -
get:声明发送一个
get
请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):
根据uri
模板和uri
变量值得到一个 GET 请求方式的。另外提供了其他的请求的方法,如:post、put、delete
等。 -
param:添加
request
的参数,假如使用需要发送json数据格式的时将不能使用这种方式,可见后面被@ResponseBody注解参数的解决方法 -
andExpect:添加
ResultMatcher
验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断); -
andDo:添加
ResultHandler
结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断); -
andReturn:最后返回相应的
MvcResult
;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断);
控制台
MockHttpServletRequest:
HTTP Method = GET
Request URI = /hello
Parameters = {}
Headers = {}
Handler:
Type = nuc.jyg.c1.web.HelloController
Method = public java.lang.String nuc.jyg.c1.web.HelloController.index()
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8], Content-Length=[11]}
Content type = application/json;charset=UTF-8
Body = Hello World
Forwarded URL = null
Redirected URL = null
Cookies = []
扩展
事物回滚
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
上面两句的作用是,让我们对数据库的操作会事务回滚,如对数据库的添加操作,在方法结束之后,会撤销我们对数据库的操作。
为什么要事务回滚?
- 测试过程对数据库的操作,会产生脏数据,影响数据的正确性
- 不方便循环测试,即假如这次将一个记录删除了,下次就无法再进行这个Junit测试了,因为该记录已经删除,将会报错。
- 如果不使用事务回滚,需要在代码中显式的对增删改数据库操作进行恢复,将多很多和测试无关的代码
这里需要注意,如果使用了事物回滚。那么有些时候对数据库内容进行修改操作后你将不能直观的看到变化。如何判断是否成功了,可以在返回的数据中带上data
字段,将修改的数据存进去传向前台。
使用andExpect方法对返回的数据进行判断,用“$.属性”获取里面的数据,如要获取返回数据中的"data.name",可以写成"$.data.name"。下面的例子是判断返回的 data.name = “测试”。
MockHttpServletRequestBuilder.andExpect(jsonPath("$.data.name", is("##"))))
不同环境的测试
@ActiveProfiles(profiles = "test")
在测试类上面指定profiles,可以改变当前spring 的profile,来达到多环境的测试
网友评论