Spring Framework的核心工作是将所有组件编织在一起,构成一个应用程序。整个过程就是读取配置说明(可以是XML、基于Java的配置、基于Groovy的配置或其他类型的配置),在应用程序上下文里初始化Bean,将Bean注入依赖它们的其他Bean中。
测试一个springboot的web项目,希望通过实际的http请求来测试,有2种可选方案:
- Spring Mock MVC:能在一个近似真实的模拟Servlet容器里测试控制器,而不用实际启动应用服务器。
- Web集成测试:在嵌入式Servlet容器(比如Tomcat或Jetty)里启动应用程序,在真正的应用服务器里执行测试。
这两种方法各有利弊。很明显,启动一个应用服务器会比模拟Servlet容器要慢一些,但毫无疑问基于服务器的测试会更接近真实环境,更接近部署到生产环境运行的情况。
早在Spring 3.2, Spring Framework就有了一套非常实用的Web应用程序测试工具,能模拟Spring MVC,不需要真实的Servlet容器也能对控制器发送HTTP请求。 Spring的Mock MVC框架模拟了Spring MVC的很多功能。它几乎和运行在Servlet容器里的应用程序一样,尽管实际情况并非如此。
要在测试里设置Mock MVC,可以使用MockMvcBuilders,该类提供了两个静态方法。
- standaloneSetup():构建一个Mock MVC,提供一个或多个手工创建并配置的控制器。
- webAppContextSetup():使用Spring应用程序上下文来构建Mock MVC,该上下文里可以包含一个或多个配置好的控制器。
两者的主要区别在于: standaloneSetup()希望你手工初始化并注入你要测试的控制器,而webAppContextSetup()则基于一个WebApplicationContext的实例,通常由Spring加载。
前者同单元测试更加接近,你可能只想让它专注于单一控制器的测试,而后者让Spring加载控制器及其依赖,以便进行完整的集成测试。
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class DemoApplicationTests {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setupMockMvc(){
mockMvc= MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void homePage() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.get("/read/A1")
).andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attributeExists("books"))
.andExpect(model().attribute("books",
Matchers.is(Matchers.empty())));
}
@Test
public void post() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post("/read/A1")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("title", "BOOK TITLE")
.param("author", "BOOK AUTHOR")
.param("isbn", "1234567890")
.param("description", "DESCRIPTION"))
.andExpect(status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.header().string("Location", "/read/A1"));
Book book=new Book();
book.setId(1l);
book.setReader("a2");
book.setTitle("BOOK TITLE");
book.setAuthor("BOOK AUTHOR");
book.setIsbn("1234567890");
book.setDescription("DESCRIPTION");
mockMvc.perform(MockMvcRequestBuilders.get("/read/A1"))
.andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attributeExists("books"))
.andExpect(model().attribute("books", hasSize(1)))
.andExpect(model().attribute("books",
Matchers.contains(samePropertyValuesAs(book))
)
);
}
}