javascript
Spring MVC测试框架入门–第2部分
這個(gè)迷你系列的第一個(gè)博客介紹了Spring MVC測(cè)試框架,并展示了其在單元測(cè)試Spring MVC Controller類中作為控制器而不是POJO進(jìn)行單元測(cè)試的用途。 現(xiàn)在是時(shí)候討論使用框架進(jìn)行集成測(cè)試了。
“集成測(cè)試”是指將Spring上下文加載到測(cè)試環(huán)境中,以便控制器可以在“端到端”測(cè)試中與合作者一起工作。
同樣,我將從Spring Social Facebook項(xiàng)目中為FacebookPostsController編寫一個(gè)測(cè)試,并且正如您所期望的那樣,該測(cè)試將是我的FacebookPostsControllerTest類的集成測(cè)試版本。 如果需要查看FacebookPostsController代碼或原始的FacebookPostsControllerTest代碼,請(qǐng)查看我的上一個(gè)博客 。 有關(guān)FacebookPostsController代碼的完整介紹,請(qǐng)參見(jiàn)Spring Social Facebook博客 。
創(chuàng)建集成測(cè)試的第一步是將Spring上下文加載到測(cè)試環(huán)境中。 這是通過(guò)在FacebookPostsControllerTest類中添加以下注釋來(lái)完成的:
@RunWith ( SpringJUnit4ClassRunner.class )或@ContextConfiguration(“ file-names”)并沒(méi)有什么新意,因?yàn)樗鼈儚腟pring 2.5開(kāi)始就出現(xiàn)了,如果您是Spring開(kāi)發(fā)人員,那么您之前可能已經(jīng)在集成測(cè)試中使用過(guò)它們。 新人是@WebAppConfiguration 。
這些批注協(xié)同工作以配置您的測(cè)試環(huán)境。 @RunWith告訴JUnit使用Spring JUnit類運(yùn)行器運(yùn)行測(cè)試。 @WebAppConfiguration告訴SpringJUnit4ClassRunner集成測(cè)試要加載的ApplicationContext應(yīng)該是WebApplicationContext ,而@ContextConfiguration用于指定加載哪個(gè)XML文件以及從何處加載。
在這種情況下,我正在加載項(xiàng)目的“ servlet-context.xml”和“ data.xml”文件。 “ servlet-context.xml”文件包含Spring Web應(yīng)用程序所需的所有標(biāo)準(zhǔn)位,例如<annotation-driven />和視圖解析器,而“ data.xml”則包含該應(yīng)用程序的Spring Social組件。 這里要注意的一點(diǎn)是,我故意使用
我想運(yùn)行端到端集成測(cè)試來(lái)訪問(wèn)文件系統(tǒng),數(shù)據(jù)庫(kù)等的偽生產(chǎn)配置文件。
這只是示例代碼,在集成測(cè)試中通常不會(huì)涉及生產(chǎn)數(shù)據(jù)庫(kù)或其他相關(guān)資源。 通常,您將配置您的應(yīng)用程序以訪問(wèn)集成測(cè)試數(shù)據(jù)庫(kù)和其他資源。 解決此問(wèn)題的一種方法是創(chuàng)建一個(gè)測(cè)試 XML配置文件。 但是,不要像我在一個(gè)項(xiàng)目中看到的那樣,為項(xiàng)目中的每個(gè)Maven模塊創(chuàng)建單獨(dú)的測(cè)試 XML文件。 原因是當(dāng)您對(duì)代碼進(jìn)行更改時(shí),最終要更改一大堆配置文件才能使集成測(cè)試再次正常工作,這既無(wú)聊又耗時(shí)。 更好的方法是擁有一個(gè)版本的XML配置,并使用Spring配置文件為不同的環(huán)境配置應(yīng)用程序。 如果確實(shí)選擇使用配置文件,則還需要將@ActiveProfiles(“profile-name”)批注添加到上面列出的其他三個(gè)批注中。 但是,這超出了本博客的范圍。
假設(shè)您使用的是自動(dòng)裝配,并且您已經(jīng)正確設(shè)置了<context:component-scan /> ,那么下一步就是將以下實(shí)例變量添加到測(cè)試類中:
@Autowired private WebApplicationContext wac;這告訴Spring將先前創(chuàng)建的WebApplicationContext注入到測(cè)試中。 然后,可以在非常簡(jiǎn)單的一行setup()方法中使用它:
@Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); }類似于此測(cè)試的“獨(dú)立” /“編程”版本, setup()方法的目的是創(chuàng)建一個(gè)mockMvc實(shí)例,然后使用它來(lái)執(zhí)行測(cè)試。 此處的區(qū)別在于,它只是通過(guò)使用WebApplicationContext作為MockMvcBuilders的參數(shù)來(lái)MockMvcBuilders 。
整理好setup()方法后,接下來(lái)要做的是編寫一個(gè)測(cè)試,我將從上一個(gè)博客中重寫testShowPostsForUser_user_is_not_signed_in()作為集成測(cè)試。 令人驚訝的是,該代碼比以前的JUnit版本更簡(jiǎn)單:
@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()代碼進(jìn)行比較,您會(huì)發(fā)現(xiàn)它幾乎相同。 唯一的區(qū)別是無(wú)需設(shè)置任何模擬對(duì)象。
在這一點(diǎn)上,我將演示testShowPostsForUser_user_is_signed_in測(cè)試的集成測(cè)試版本,但這確實(shí)有些棘手。 原因是要掌握他們的Facebook帖子列表,用戶必須登錄其Facebook帳戶,這意味著在正確的必要HttpServletRequest對(duì)象之前,需要對(duì)服務(wù)器進(jìn)行多次順序調(diào)用狀態(tài)以方便致電Facebook檢索帖子列表。 對(duì)于示例代碼來(lái)說(shuō),這似乎有點(diǎn)太復(fù)雜了,這是我不想在
真實(shí)的項(xiàng)目。
我不是將這種復(fù)雜性視為Spring MVC測(cè)試框架的局限性,而是要強(qiáng)調(diào)最佳實(shí)踐,這是確保對(duì)服務(wù)器的調(diào)用盡可能獨(dú)立且原子。
當(dāng)然,我可以使用模擬對(duì)象或創(chuàng)建虛擬Facebook服務(wù),但是,這超出了本博客的范圍。
一個(gè)獨(dú)立的一個(gè)很好的例子,原子服務(wù)器調(diào)用是REST調(diào)用testConfirmPurchases_selection_1_returns_a_hat(...)用于測(cè)試的OrderController從我采取類的Spring MVC,Ajax和JSON第2部分-服務(wù)器端代碼博客。 此代碼在Ajax博客中進(jìn)行了全面描述,它請(qǐng)求購(gòu)買確認(rèn),并以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看起來(lái)像這樣:
{"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"}下面以冗長(zhǎng)的樣式顯示了測(cè)試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希望您編寫的代碼; 但是,以冗長(zhǎng)的格式更容易討論正在發(fā)生的事情。 該方法的結(jié)構(gòu)類似于第1部分中討論的testShowPostsForUser_user_is_signed_in(...)方法。第一步是使用靜態(tài)MockMvcRequestBuilders.post(...)方法創(chuàng)建MockHttpServletRequestBuilder類型的postRequest對(duì)象。 值"1"的"selection"參數(shù)將添加到結(jié)果對(duì)象中。
然后將postRequest傳遞給mockMvc.perform(...)方法,并返回一個(gè)ResultActions對(duì)象。
然后使用andExpect(...)方法驗(yàn)證ResultActions對(duì)象,以檢查HTTP狀態(tài)(ok = 200)和內(nèi)容類型為"application/json;charset=UTF-8" 。
此外,我還添加了一個(gè)andDo(print())方法調(diào)用,以顯示HttpServletRequest和HttpServletResponse對(duì)象的狀態(tài)。 該調(diào)用的輸出如下所示:
MockHttpServletRequest:HTTP Method = POSTRequest URI = /confirmParameters = {selection=[1]}Headers = {}Handler:Type = com.captaindebug.store.OrderControllerMethod = public com.captaindebug.store.beans.OrderForm com.captaindebug.store.OrderController.confirmPurchases(com.captaindebug.store.beans.UserSelections)Resolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:MockHttpServletResponse:Status = 200Error message = nullHeaders = {Content-Type=[application/json;charset=UTF-8]}Content type = application/json;charset=UTF-8Body = {"items":[{"id":1,"description":"A nice hat","name":"Hat","price":12.34}],"purchaseId":"d1d0eba6-51fa-415f-ac4e-8fa2eaeaaba9"}Forwarded URL = nullRedirected URL = nullCookies = []最后一項(xiàng)測(cè)試使用靜態(tài)MockMvcResultMatchers.jsonPath(...)檢查"$items[0].description"的JSON路徑的值是否為"A nice hat" 。 為了使用jsonPath(...)靜態(tài)方法,您必須在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數(shù)據(jù)中選擇性提取字段的方法。 它基于XML的XPath思想。
顯然,我上面用過(guò)的冗長(zhǎng)風(fēng)格不需要編寫測(cè)試。 下面的代碼顯示與Spring的Guy設(shè)計(jì)的相同代碼:
@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")); }所以,僅此而已。 概括地說(shuō),我們的想法是向您的單元測(cè)試中添加適當(dāng)?shù)淖⑨?#xff0c;以便Spring加載您的XML配置以創(chuàng)建WebApplicationContext 。 然后將其注入到您的測(cè)試中,并在創(chuàng)建mockMvc時(shí)作為參數(shù)傳遞給Spring MVC Test框架。 然后編寫測(cè)試,其想法是將適當(dāng)構(gòu)造的MockMvcRequestBuilders對(duì)象傳遞給mockMvc.perform(...)方法,然后將其返回值聲明為通過(guò)或失敗測(cè)試。
該博客的代碼可在GitHub上找到: https : //github.com/roghughe/captaindebug/在Facebook和Ajax-JSON項(xiàng)目中。
翻譯自: https://www.javacodegeeks.com/2013/07/getting-started-with-springs-mvc-test-framework-part-2.html
總結(jié)
以上是生活随笔為你收集整理的Spring MVC测试框架入门–第2部分的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring MVC控制器的单元测试:配
- 下一篇: 2013年测试基于Web的Spring应