测试 Web 应用程序
模拟 Spring MVC
要在测试里设置Mock MVC,可以使用 MockMvcBuilders
,该类提供了两个静态方法。
standaloneSetup()
:
构建一个Mock MVC,提供一个或多个手工创建并配置的控制器。webAppContextSetup()
:
使用Spring应用程序上下文来构建Mock MVC,该上下文里可以包含一个或多个配置好的控制器。
两者的主要区别在于:
standaloneSetup()
希望你手工初始化并注入你要测试的控制器,而webAppContextSetup()
则基于一个WebApplicationContext
的实例,通常由Spring加载。standaloneSetup()
同单元测试更加接近,你可能只想让它专注于单一控制器的测试,而webAppContextSetup()
让Spring加载控制器及其依赖,以便进行完整的集成测试。
我们要用的是 webAppContextSetup() 。
测试执行入口
-
@RunWith(SpringJUnit4ClassRunner.class)
基于JUnit的应用程序测试里加载Spring应用程 序上下文。 - @SpringBootTest(classes = ReadingListApplication.class)
Spring完成了 ReadingListController 的初始化,并从Spring Boot自动配置的应用程序上下文里将其注入。 - @WebAppConfiguration
使用Spring应用程序上下文来构建Mock MVC。 - 使用 @Autowired 将 WebApplicationContext 作为实例变量注入测试类。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ReadingListApplication.class)
@WebAppConfiguration // 开启Web上下文测试
public class MockMvcWebTests {
@Autowired
private WebApplicationContext webContext; // 注入 WebApplicationContext
private MockMvc mockMvc;
@Before
public void setupMockMvc() {
// 设置 MockMvc
mockMvc = MockMvcBuilders.webAppContextSetup(webContext).build();
}
}
单元测试代码
测试 Web 安全
Spring Security能让你非常方便地测试安全加固后的Web应用程序。
为了利用这点优势,必须在项目里添加Spring Security的测试模块。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
加载 Spring Security 的测试模块之后,只需在创建 MockMvc 实例时运用 Spring Security的配置器。
springSecurity() 方法返回了一个Mock MVC配置器,为Mock MVC开启了Spring Security 支持。
@Before
public void setupMockMvc() { // 设置 MockMvc
mockMvc = MockMvcBuilders.webAppContextSetup(webContext)
.apply(springSecurity()).build();
}
未经身份验证示例
如果请求未经身份验证,重定向到登录页面:
@Test
public void homePage_unauthenticatedUser() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", "http://localhost/login"));
}
经过身份验证示例
对于经过身份验证的请求,Spring Security提供了两个注解。
- @WithMockUser :
加载安全上下文,其中包含一个 UserDetails ,使用了给定的用户名、密码和授权。 - @WithUserDetails :
根据给定的用户名查找 UserDetails 对象,加载安全上下文。
@WithMockUser 示例
@WithMockUser
绕过了对 UserDetails 对象的正常查询,用给定的值创建了一
个 UserDetails 对象取而代之。
所以,使用@WithMockUser
来虚拟一个用户,这个用户可以是不存在的:
@Test
@WithMockUser(username="craig", password="password", roles="READER")
public void homePage_authenticatedUser1() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attribute("books", hasSize(0)))
.andExpect(model().attribute("amazonID", "Jueee"));
}
@WithUserDetails 示例
使用@WithUserDetails
,通过 UserDetailsService.loadUserByUsername
根据用户名加载一个用户。
从而,这个用户必须在运行单元测试之前就存在
@Test
@WithUserDetails(value="craig") // 使用craig用户
public void homePage_authenticatedUser2() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attribute("books", hasSize(0)))
.andExpect(model().attribute("amazonID", "Jueee"));
}
也必须将 SecurityConfig 类 中的 UserDetailsService.loadUserByUsername
进行独立。
配置了一个 UserDetailsService Bean,它会根据给定的用户名查找 并返回一个 Reader 对象:
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails userDetails = readerRepository.getOne(username);
if (userDetails != null) {
return userDetails;
}
throw new UsernameNotFoundException("User '" + username + "' not found.");
}
};
}