Spring 也提供了完善的測試框架,我們可以方便的測試Spring Web MVC應(yīng)用程序。為了使用這個測試框架,我們需要添加它的依賴項。
compile group: 'org.springframework', name: 'spring-test', version: '4.3.6.RELEASE'
服務(wù)端測試
我們可以利用Spring提供的Mock對象來測試我們Spring程序的服務(wù)端行為。通過這些Mock對象,我們可以建立一個假的服務(wù)器,然后發(fā)送一些假的請求,來測試我們的程序。為了能簡潔的編寫測試代碼,我們最好在代碼中使用靜態(tài)導(dǎo)入將MockMvcRequestBuilders.*
、MockMvcResultMatchers.*
和MockMvcBuilders.*
引入到代碼中。
建立測試環(huán)境
建立Spring Web MVC的測試環(huán)境和普通的Spring 單元測試略有不同。我們需要使用@WebAppConfiguration注解測試類。Spring知道這是一個Web MVC測試之后,就會使用@ContextConfiguration注解中的配置文件來創(chuàng)建一個WebApplicationContext,然后我們可以將其注入到測試類中。然后要做的事情就是創(chuàng)建MockMvc對象,我們大部分測試都要通過該對象進(jìn)行。
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public class UserControllerTest {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void init() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}
當(dāng)然,如果只需要測試某個控制器,我們完全可以不加載完整的配置文件。這時候可以使用MockMvcBuilders.standaloneSetup來僅使用Spring默認(rèn)配置配置某個控制器。
public class SimpleTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
}
發(fā)起請求
這里假定代碼中已經(jīng)靜態(tài)導(dǎo)入上面提到的一些類。
我們使用MockMvc的perform方法發(fā)起一個HTTP請求,這個請求可以是get、post等,然后我們還可以為請求設(shè)置accept等信息。
mockMvc.perform(post("/users/{id}", 42).accept(MediaType.ALL));
當(dāng)然也可以發(fā)起文件上傳請求。
mockMvc.perform(fileUpload("/upload").file("file", file.getBytes("UTF-8")));
我們可以直接在請求中包含參數(shù)。
mockMvc.perform(get("/users?user={foo}", "bar"));
也可以使用param方法傳遞參數(shù),這種方式可以傳遞POST表單數(shù)據(jù)。
mockMvc.perform(post("/users").param("foo", "bar"));
如有需要,我們還可以為請求添加contextPath和servletPath。
mockMvc.perform(get("/myproject/contextpath/users").contextPath("/myproject").servletPath("/contextpath"))
期望結(jié)果
發(fā)起請求之后,我們需要驗證請求是否正確處理。這時候需要在perform方法之后再調(diào)用andExpect方法。我們可以期望獲得各種結(jié)果,最常用的就是獲得各種響應(yīng)碼。下面的例子期望首頁可以正常訪問。當(dāng)然status()方法也提供了其他了響應(yīng)碼方法來滿足我們的需求。
mockMvc.perform(get("/index")).andExpect(status().isOk());
還可以期望結(jié)果的媒體類型。
mvc.perform(get("/users.xml"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_XML));
有時候需要驗證請求返回的模型,比如下面就斷言結(jié)果會有錯誤。
mockMvc.perform(post("/updateInfo"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("user"));
某些情況下需要查看請求或響應(yīng)的內(nèi)容。我們可以調(diào)用Spring提供的print或log方法來打印信息或者記錄日志。默認(rèn)情況下print方法會將結(jié)果輸出到System.out
,而log方法會將日志記錄到調(diào)試級別的org.springframework.test.web.servlet.result
包下。
mockMvc.perform(post("/updateInfo"))
.andExpect(status().isOk())
.andDo(print())
.andExpect(model().attributeHasErrors("user"));
有時候需要詳細(xì)檢驗返回結(jié)果。我們可以在所有期望方法的最后添加andReturn方法。該方法會返回一個MvcResult對象,我們可以調(diào)用該對象的各種get方法獲取我們需要的信息。
MvcResult mvcResult = mockMvc.perform(post("/listUsers")).andExpect(status().isOk()).andReturn();
如果某些期望是所有方法都需要的,我們可以將它設(shè)置為共用的。但是一旦設(shè)置就無法更改。所以如果我們不需要某個共用期望的話就只能創(chuàng)建一個新的MockMvc對象了。
standaloneSetup(new UserController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
如果我們希望在單個控制器中添加過濾器的話,可以在建立MockMvc對象的時候指定過濾器。
mockMvc = standaloneSetup(new UserController()).addFilters(new CharacterEncodingFilter()).build();
spring-mvc-showcase是一個Spring官方開發(fā)的示例程序,包含了Spring Web MVC的例子和基本功能,也包含了所有的服務(wù)端測試代碼。這也是一個很好的學(xué)習(xí)資源。
HtmlUnit集成
MockMvc雖然好用,但是畢竟是一個假的測試,它沒有實際運(yùn)行的服務(wù)器, 也不會進(jìn)行實際的視圖渲染、轉(zhuǎn)發(fā)和重定向等操作。如果我們希望測試實際的HTML視圖、JavaScript驗證等功能,就需要使用HtmlUnit。
我們需要在項目中引用HtmlUnit的依賴。
compile group: 'net.sourceforge.htmlunit', name: 'htmlunit', version: '2.24'
然后初始化一個WebClient。
@Autowired
WebApplicationContext context;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
這樣配置的話,默認(rèn)所有localhost
下的請求就會自動通過MockMvc對象來訪問,不需要實際HTTP連接,這方便我們本機(jī)測試。而其他域名會正常使用網(wǎng)絡(luò)來連接,這可以讓我們測試CDN等的狀況。
然后我們可以使用WebClient來創(chuàng)建測試了。這里我直接貼Spring文檔里的例子了。我們從例子中可以看到,WebClient的使用方法和使用普通的JavaScript操作DOM差不多。下面是創(chuàng)建請求的代碼。
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
下面是執(zhí)行驗證的代碼。這里的斷言使用了AssertJ庫。
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
從這里我們就可以看到直接使用HtmlUnit的缺點了,那就是代碼笨重,不好看。Spring還提供了另外兩個類庫WebDriver和Geb來簡化HtmlUnit的測試過程,詳見Spring 參考文檔 HtmlUnit集成
客戶端的REST測試
如果需要客戶端測試REST程序,Spring也提供了相關(guān)功能。直接來看Spring官方的例子。我們需要先創(chuàng)建一個RestTemplate對象,然后創(chuàng)建MockRestServiceServer并綁定到RestTemplate上。然后使用MockRestServiceServer的expect方法發(fā)起請求并測試結(jié)果。最后調(diào)用verify方法驗證是否滿足所有期望。這種方式不需要啟動實際服務(wù)器,效率很高。
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
// 使用RestTemplate進(jìn)行其他測試 ...
mockServer.verify();
客戶端測試也可以和服務(wù)端測試結(jié)合起來。我們可以利用MockMvc對象來創(chuàng)建RestTemplate,這樣就會使用服務(wù)端的邏輯來測試代碼而不需要啟動實際服務(wù)器。
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// 使用RestTemplate進(jìn)行其他測試 ...
mockServer.verify();