天天看點

Spring Web MVC架構(十一) Spring Web MVC測試架構

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();
           

參考資料

Spring 參考文檔 15.6. Spring MVC Test Framework