Spring 也提供了完善的測試架構,我們可以友善的測試Spring Web MVC應用程式。為了使用這個測試架構,我們需要添加它的依賴項。
compile group: 'org.springframework', name: 'spring-test', version: '4.3.6.RELEASE'
服務端測試
我們可以利用Spring提供的Mock對象來測試我們Spring程式的服務端行為。通過這些Mock對象,我們可以建立一個假的伺服器,然後發送一些假的請求,來測試我們的程式。為了能簡潔的編寫測試代碼,我們最好在代碼中使用靜态導入将
MockMvcRequestBuilders.*
、
MockMvcResultMatchers.*
和
MockMvcBuilders.*
引入到代碼中。
建立測試環境
建立Spring Web MVC的測試環境和普通的Spring 單元測試略有不同。我們需要使用@WebAppConfiguration注解測試類。Spring知道這是一個Web MVC測試之後,就會使用@ContextConfiguration注解中的配置檔案來建立一個WebApplicationContext,然後我們可以将其注入到測試類中。然後要做的事情就是建立MockMvc對象,我們大部分測試都要通過該對象進行。
@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();
}
}
當然,如果隻需要測試某個控制器,我們完全可以不加載完整的配置檔案。這時候可以使用MockMvcBuilders.standaloneSetup來僅使用Spring預設配置配置某個控制器。
public class SimpleTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
}
發起請求
這裡假定代碼中已經靜态導入上面提到的一些類。
我們使用MockMvc的perform方法發起一個HTTP請求,這個請求可以是get、post等,然後我們還可以為請求設定accept等資訊。
mockMvc.perform(post("/users/{id}", 42).accept(MediaType.ALL));
當然也可以發起檔案上傳請求。
mockMvc.perform(fileUpload("/upload").file("file", file.getBytes("UTF-8")));
我們可以直接在請求中包含參數。
mockMvc.perform(get("/users?user={foo}", "bar"));
也可以使用param方法傳遞參數,這種方式可以傳遞POST表單資料。
mockMvc.perform(post("/users").param("foo", "bar"));
如有需要,我們還可以為請求添加contextPath和servletPath。
mockMvc.perform(get("/myproject/contextpath/users").contextPath("/myproject").servletPath("/contextpath"))
期望結果
發起請求之後,我們需要驗證請求是否正确處理。這時候需要在perform方法之後再調用andExpect方法。我們可以期望獲得各種結果,最常用的就是獲得各種響應碼。下面的例子期望首頁可以正常通路。當然status()方法也提供了其他了響應碼方法來滿足我們的需求。
mockMvc.perform(get("/index")).andExpect(status().isOk());
還可以期望結果的媒體類型。
mvc.perform(get("/users.xml"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_XML));
有時候需要驗證請求傳回的模型,比如下面就斷言結果會有錯誤。
mockMvc.perform(post("/updateInfo"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("user"));
某些情況下需要檢視請求或響應的内容。我們可以調用Spring提供的print或log方法來列印資訊或者記錄日志。預設情況下print方法會将結果輸出到
System.out
,而log方法會将日志記錄到調試級别的
org.springframework.test.web.servlet.result
包下。
mockMvc.perform(post("/updateInfo"))
.andExpect(status().isOk())
.andDo(print())
.andExpect(model().attributeHasErrors("user"));
有時候需要詳細檢驗傳回結果。我們可以在所有期望方法的最後添加andReturn方法。該方法會傳回一個MvcResult對象,我們可以調用該對象的各種get方法擷取我們需要的資訊。
MvcResult mvcResult = mockMvc.perform(post("/listUsers")).andExpect(status().isOk()).andReturn();
如果某些期望是所有方法都需要的,我們可以将它設定為共用的。但是一旦設定就無法更改。是以如果我們不需要某個共用期望的話就隻能建立一個新的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官方開發的示例程式,包含了Spring Web MVC的例子和基本功能,也包含了所有的服務端測試代碼。這也是一個很好的學習資源。
HtmlUnit內建
MockMvc雖然好用,但是畢竟是一個假的測試,它沒有實際運作的伺服器, 也不會進行實際的視圖渲染、轉發和重定向等操作。如果我們希望測試實際的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();
}
這樣配置的話,預設所有
localhost
下的請求就會自動通過MockMvc對象來通路,不需要實際HTTP連接配接,這友善我們本機測試。而其他域名會正常使用網絡來連接配接,這可以讓我們測試CDN等的狀況。
然後我們可以使用WebClient來建立測試了。這裡我直接貼Spring文檔裡的例子了。我們從例子中可以看到,WebClient的使用方法和使用普通的JavaScript操作DOM差不多。下面是建立請求的代碼。
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();
下面是執行驗證的代碼。這裡的斷言使用了
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也提供了相關功能。直接來看Spring官方的例子。我們需要先建立一個RestTemplate對象,然後建立MockRestServiceServer并綁定到RestTemplate上。然後使用MockRestServiceServer的expect方法發起請求并測試結果。最後調用verify方法驗證是否滿足所有期望。這種方式不需要啟動實際伺服器,效率很高。
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
// 使用RestTemplate進行其他測試 ...
mockServer.verify();
用戶端測試也可以和服務端測試結合起來。我們可以利用MockMvc對象來建立RestTemplate,這樣就會使用服務端的邏輯來測試代碼而不需要啟動實際伺服器。
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// 使用RestTemplate進行其他測試 ...
mockServer.verify();