这个迷你系列的第一个博客介绍了Spring MVC测试框架,并展示了其在单元测试Spring MVC Controller类中作为控制器而不是POJO进行单元测试的用途。 现在是时候讨论使用框架进行集成测试了。
“集成测试”是指将Spring上下文加载到测试环境中,以便控制器可以在“端到端”测试中与合作者一起工作。
同样,我将从Spring Social Facebook项目中为FacebookPostsController
编写一个测试,并且正如您所期望的那样,该测试将是我的FacebookPostsControllerTest
类的集成测试版本。 如果需要查看FacebookPostsController
代码或原始的FacebookPostsControllerTest
代码,请查看我的上一个博客 。 有关FacebookPostsController
代码的完整介绍,请参见Spring Social Facebook博客 。
创建集成测试的第一步是将Spring上下文加载到测试环境中。 这是通过在FacebookPostsControllerTest
类中添加以下注释来完成的:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml",
"file:src/main/webapp/WEB-INF/spring/data.xml" })
public class FacebookPostsControllerTest {
@RunWith ( SpringJUnit4ClassRunner.class )或@ContextConfiguration(“ file-names”)并没有什么新意,因为它们从Spring 2.5开始就出现了,如果您是Spring开发人员,那么您之前可能已经在集成测试中使用过它们。 新人是@WebAppConfiguration 。
这些批注协同工作以配置您的测试环境。 @RunWith
告诉JUnit使用Spring JUnit类运行器运行测试。 @WebAppConfiguration
告诉SpringJUnit4ClassRunner
集成测试要加载的ApplicationContext
应该是WebApplicationContext
,而@ContextConfiguration
用于指定加载哪个XML文件以及从何处加载。
在这种情况下,我正在加载项目的“ servlet-context.xml”和“ data.xml”文件。 “ servlet-context.xml”文件包含Spring Web应用程序所需的所有标准位,例如<annotation-driven />和视图解析器,而“ data.xml”则包含该应用程序的Spring Social组件。 这里要注意的一点是,我故意使用
我想运行端到端集成测试来访问文件系统,数据库等的伪生产配置文件。
这只是示例代码,在集成测试中通常不会涉及生产数据库或其他相关资源。 通常,您将配置您的应用程序以访问集成测试数据库和其他资源。 解决此问题的一种方法是创建一个测试 XML配置文件。 但是,不要像我在一个项目中看到的那样,为项目中的每个Maven模块创建单独的测试 XML文件。 原因是当您对代码进行更改时,最终要更改一大堆配置文件才能使集成测试再次正常工作,这既无聊又耗时。 更好的方法是拥有一个版本的XML配置,并使用Spring配置文件为不同的环境配置应用程序。 如果确实选择使用配置文件,则还需要将@ActiveProfiles(“profile-name”)
批注添加到上面列出的其他三个批注中。 但是,这超出了本博客的范围。
假设您使用的是自动装配,并且您已经正确设置了<context:component-scan />
,那么下一步就是将以下实例变量添加到测试类中:
@Autowired
private WebApplicationContext wac;
这告诉Spring将先前创建的WebApplicationContext
注入到测试中。 然后,可以在非常简单的一行setup()
方法中使用它:
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
类似于此测试的“独立” /“编程”版本, setup()
方法的目的是创建一个mockMvc
实例,然后使用它来执行测试。 此处的区别在于,它只是通过使用WebApplicationContext
作为MockMvcBuilders
的参数来MockMvcBuilders
。
整理好setup()
方法后,接下来要做的是编写一个测试,我将从上一个博客中重写testShowPostsForUser_user_is_not_signed_in()
作为集成测试。 令人惊讶的是,该代码比以前的JUnit版本更简单:
@Test
public void testShowPostsForUser_user_is_not_signed_in() throws Exception {
ResultActions resultActions = mockMvc.perform(get("/posts").accept(MediaType.ALL));
resultActions.andExpect(status().isOk());
resultActions.andExpect(view().name("signin"));
}
如果将此代码与我以前的博客中的testShowPostsForUser_user_is_not_signed_in()
代码进行比较,您会发现它几乎相同。 唯一的区别是无需设置任何模拟对象。
在这一点上,我将演示testShowPostsForUser_user_is_signed_in
测试的集成测试版本,但这确实有些棘手。 原因是要掌握他们的Facebook帖子列表,用户必须登录其Facebook帐户,这意味着在正确的必要HttpServletRequest
对象之前,需要对服务器进行多次顺序调用状态以方便致电Facebook检索帖子列表。 对于示例代码来说,这似乎有点太复杂了,这是我不想在
真实的项目。
我不是将这种复杂性视为Spring MVC测试框架的局限性,而是要强调最佳实践,这是确保对服务器的调用尽可能独立且原子。
当然,我可以使用模拟对象或创建虚拟Facebook服务,但是,这超出了本博客的范围。
一个独立的一个很好的例子,原子服务器调用是REST调用testConfirmPurchases_selection_1_returns_a_hat(...)
用于测试的OrderController
从我采取类的Spring MVC,Ajax和JSON第2部分-服务器端代码博客。 此代码在Ajax博客中进行了全面描述,它请求购买确认,并以JSON的形式返回。
下面OrderController
了返回JSON的OrderController
代码:
/**
* Create an order form for user confirmation
*/
@RequestMapping(value = "/confirm", method = RequestMethod.POST)
public @ResponseBody
OrderForm confirmPurchases(@ModelAttribute("userSelections") UserSelections userSelections) {
logger.debug("Confirming purchases...");
OrderForm orderForm = createOrderForm(userSelections.getSelection());
return orderForm;
}
private OrderForm createOrderForm(List<String> selections) {
List<Item> items = findItemsInCatalogue(selections);
String purchaseId = getPurchaseId();
OrderForm orderForm = new OrderForm(items, purchaseId);
return orderForm;
}
private List<Item> findItemsInCatalogue(List<String> selections) {
List<Item> items = new ArrayList<Item>();
for (String selection : selections) {
Item item = catalogue.findItem(Integer.valueOf(selection));
items.add(item);
}
return items;
}
private String getPurchaseId() {
return UUID.randomUUID().toString();
}
尽管它返回的JSON看起来像这样:
{"items":[{"id":1,"description":"description","name":"name","price":1.00},
{"id":2,"description":"description2","name":"name2","price":2.00}],
"purchaseId":"aabf118e-abe9-4b59-88d2-0b897796c8c0"}
下面以冗长的样式显示了测试testConfirmPurchases_selection_1_returns_a_hat(...)
的代码。
@Test
public void testConfirmPurchases_selection_1_returns_a_hat() throws Exception {
final String mediaType = "application/json;charset=UTF-8";
MockHttpServletRequestBuilder postRequest = post("/confirm");
postRequest = postRequest.param("selection", "1");
ResultActions resultActions = mockMvc.perform(postRequest);
resultActions.andDo(print());
resultActions.andExpect(content().contentType(mediaType));
resultActions.andExpect(status().isOk());
// See http://goessner.net/articles/JsonPath/ for more on JSONPath
ResultMatcher pathMatcher = jsonPath("$items[0].description").value("A nice hat");
resultActions.andExpect(pathMatcher);
}
上面的代码不是Spring Guys希望您编写的代码; 但是,以冗长的格式更容易讨论正在发生的事情。 该方法的结构类似于第1部分中讨论的testShowPostsForUser_user_is_signed_in(...)
方法。第一步是使用静态MockMvcRequestBuilders.post(...)
方法创建MockHttpServletRequestBuilder
类型的postRequest
对象。 值"1"
的"selection"
参数将添加到结果对象中。
然后将postRequest
传递给mockMvc.perform(...)
方法,并返回一个ResultActions
对象。
然后使用andExpect(...)
方法验证ResultActions
对象,以检查HTTP状态(ok = 200)和内容类型为"application/json;charset=UTF-8"
。
此外,我还添加了一个andDo(print())
方法调用,以显示HttpServletRequest
和HttpServletResponse
对象的状态。 该调用的输出如下所示:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /confirm
Parameters = {selection=[1]}
Headers = {}
Handler:
Type = com.captaindebug.store.OrderController
Method = public com.captaindebug.store.beans.OrderForm com.captaindebug.store.OrderController.confirmPurchases(com.captaindebug.store.beans.UserSelections)
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"items":[{"id":1,"description":"A nice hat","name":"Hat","price":12.34}],"purchaseId":"d1d0eba6-51fa-415f-ac4e-8fa2eaeaaba9"}
Forwarded URL = null
Redirected URL = null
Cookies = []
最后一项测试使用静态MockMvcResultMatchers.jsonPath(...)
检查"$items[0].description"
的JSON路径的值是否为"A nice hat"
。 为了使用jsonPath(...)
静态方法,您必须在POM.xml中包含JSON Path模块以解析JSON。
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
JSonPath是一种从JSon数据中选择性提取字段的方法。 它基于XML的XPath思想。
显然,我上面用过的冗长风格不需要编写测试。 下面的代码显示与Spring的Guy设计的相同代码:
@Test
public void testConfirmPurchases_spring_style() throws Exception {
mockMvc.perform(post("/confirm").param("selection", "1")).andDo(print())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(jsonPath("$items[0].description").value("A nice hat"));
}
所以,仅此而已。 概括地说,我们的想法是向您的单元测试中添加适当的注释,以便Spring加载您的XML配置以创建WebApplicationContext
。 然后将其注入到您的测试中,并在创建mockMvc
时作为参数传递给Spring MVC Test框架。 然后编写测试,其想法是将适当构造的MockMvcRequestBuilders
对象传递给mockMvc.perform(...)
方法,然后将其返回值声明为通过或失败测试。
该博客的代码可在GitHub上找到: https : //github.com/roghughe/captaindebug/在Facebook和Ajax-JSON项目中。
翻译自: https://www.javacodegeeks.com/2013/07/getting-started-with-springs-mvc-test-framework-part-2.html