Unit Testing of Spring MVC


試驗1:作的條目不發現
首先,咱們必須確保咱們的應用是工做性質所作條目不發現。咱們能夠寫的測試以確保經過如下步驟:java

一、配置的模擬對象時拋出一個todonotfoundexception findbyid()方法被調用和請求的待辦事項條目ID 1L。
二、執行一個GET請求的URL /作/ 1′。
三、確認HTTP狀態碼返回404。
四、確保返回的視圖名稱是「錯誤/ 404′。
五、確保請求轉發到URL「/WEB-INF/JSP /錯誤/ 404 JSP」。
六、驗證的todoservice接口findbyid()方法被稱爲只有一次正確的方法參數(1)。
七、請確認沒有其餘的模仿對象的方法都是在這個測試被稱爲。web

咱們的單元測試的源代碼以下:spring

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;數據庫

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;數組

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {spring-mvc

    private MockMvc mockMvc;session

    @Autowired
    private TodoService todoServiceMock;mvc

    //Add WebApplicationContext field hereapp

    //The setUp() method is omitted.框架

    @Test
    public void findById_TodoEntryNotFound_ShouldRender404View() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));

        verify(todoServiceMock, times(1)).findById(1L);
        verifyZeroInteractions(todoServiceMock);
    }
}


試驗2:作條目被發現
第二,咱們必須寫一個測試以確保控制器正常工做時,所有的入口被發現。咱們能夠經過下面的步驟:
一、建立待辦事項的對象是回來時,咱們的服務調用的方法。再次,咱們建立返回的Todo對象經過使用咱們的測試數據生成器。
二、配置咱們的模擬對象建立對象時返回作的findbyid()方法是採用參數1。
三、執行一個GET請求的URL /作/ 1′。
四、確認HTTP狀態碼返回200。
五、確保返回的視圖名稱是「作/視圖」。
六、確保請求轉發到URL「/WEB-INF/JSP /作/視圖JSP」。
七、驗證的模型被稱爲「對象ID 1L。
八、驗證的模型對象,被稱爲「的描述是「lorem ipsum」。
九、驗證的模型對象,被稱爲「標題是「foo」。
十、確保咱們的模擬對象的findbyid()方法被稱爲只有一次正確的方法參數(1)。
十一、確保模擬對象的其餘方法不在咱們的測試要求。

咱們的單元測試的源代碼以下:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryFound_ShouldAddTodoEntryToModelAndRenderViewTodoEntryView() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

        when(todoServiceMock.findById(1L)).thenReturn(found);

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/view"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/view.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));

        verify(todoServiceMock, times(1)).findById(1L);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

在添加待辦事項表格表單提交

再次,咱們將首先在咱們會爲它編寫單元測試將在咱們的控制方法的預期行爲一看。

預期的行爲

該控制器的方法處理的添加待辦事項報名表是經過如下這些步驟的形式提交:

一、它處理POST請求發送到URL /作/添加」。
二、它會檢查做爲方法的參數不應BindingResult物體有任何錯誤。若是發現錯誤,則返回窗體視圖的名稱。
三、它增長了經過調用接口的todoservice add()方法的一個新的待辦事項的進入和經過表單對象做爲方法參數。該方法建立一個新的待辦事項條目並返回它。
四、它創造了對添加待辦事項輸入反饋的消息,將消息添加到redirectattributes對象做爲方法參數。
五、它增長了額外的任務進入redirectattributes對象ID。
六、它返回一個重定向視圖將請求重定向到登陸頁面查看待辦事項名稱。

的todocontroller類相關的部分看起來以下:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.Locale;

@Controller
@SessionAttributes("todo")
public class TodoController {

    private final TodoService service;

    private final MessageSource messageSource;

    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
        if (result.hasErrors()) {
            return "todo/add";
        }

        Todo added = service.add(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
        attributes.addAttribute("id", added.getId());

        return createRedirectViewPath("todo/view");
    }

    private void addFeedbackMessage(RedirectAttributes attributes, String messageCode, Object... messageParameters) {
        String localizedFeedbackMessage = getMessage(messageCode, messageParameters);
        attributes.addFlashAttribute("feedbackMessage", localizedFeedbackMessage);
    }

    private String getMessage(String messageCode, Object... messageParameters) {
        Locale current = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageCode, messageParameters, current);
    }

    private String createRedirectViewPath(String requestMapping) {
        StringBuilder redirectViewPath = new StringBuilder();
        redirectViewPath.append("redirect:");
        redirectViewPath.append(requestMapping);
        return redirectViewPath.toString();
    }
}

咱們能夠看到,該控制器採用tododto對象做爲一個窗體對象。該tododto類是一個簡單的DTO類源代碼以下:

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class TodoDTO {

    private Long id;

    @Length(max = 500)
    private String description;

    @NotEmpty
    @Length(max = 100)
    private String title;

    //Constructor and other methods are omitted.
}

該tododto類聲明瞭一些驗證約束以下:
 一、一項不作標題是空的。
 二、描述的最大長度是500個字符。
 三、標題的最大長度是100個字符。
若是咱們想測試,咱們應該爲這個控制器的方法寫,它是明確的,咱們必須確保
 一、該控制器的方法是工做性質當驗證失敗。
 二、該控制器的方法是工做性質當todo條目被添加到數據庫。
讓咱們看看如何寫這些測試。

試驗1:驗證失敗
首先,咱們要寫一個測試,確保咱們的控制器的方法是正常工做,當驗證失敗。咱們能夠這樣寫測試按如下步驟:
 一、建立一個標題,101字。
 二、建立一個描述這501個字符。
 三、經過使用咱們的測試數據生成器建立一個新的tododto對象。設置標題和對象的描述。
 四、執行POST請求的URL /作/添加」。設置請求的應用程序/窗體-urlencoded內容類型」。確保內容的形式對象發送請求的身體。設置窗體對象爲會話。
 五、確認HTTP狀態碼返回200。
 六、確認返回的視圖名稱是「作/添加」。
 七、驗證請求轉發到URL「/WEB-INF/JSP /作/添加JSP」。
 八、驗證咱們的模型的屬性是場錯誤的標題和描述領域。
 九、確保咱們的模型屬性ID無效。
 十、確保咱們的模型屬性的描述是正確的。
 十一、確保咱們的模型屬性的標題是正確的。
 十二、確保咱們的模擬對象方法不在測試過程當中被稱爲。
咱們的單元測試的源代碼以下:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

        TodoDTO formObject =  new TodoDTOBuilder()
                .description(description)
                .title(title)
                .build();

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));

        verifyZeroInteractions(todoServiceMock);
    }
}

咱們的測試用例的testutil類調用靜態方法。這些方法以下:
 一、該createstringwithlength(int length)方法建立一個新的具備給定長度的字符串對象並返回建立的對象。
 二、該convertobjecttoformurlencodedbytes(對象)的方法的對象轉換成URL編碼的字符串對象,做爲一個字節數組返回的字符串對象的內容。
的testutil類的源代碼以下:


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class TestUtil {

    public static byte[] convertObjectToFormUrlEncodedBytes(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        Map<String, Object> propertyValues = mapper.convertValue(object, Map.class);

        Set<String> propertyNames = propertyValues.keySet();
        Iterator<String> nameIter = propertyNames.iterator();

        StringBuilder formUrlEncoded = new StringBuilder();

        for (int index=0; index < propertyNames.size(); index++) {
            String currentKey = nameIter.next();
            Object currentValue = propertyValues.get(currentKey);

            formUrlEncoded.append(currentKey);
            formUrlEncoded.append("=");
            formUrlEncoded.append(currentValue);

            if (nameIter.hasNext()) {
                formUrlEncoded.append("&");
            }
        }

        return formUrlEncoded.toString().getBytes();
    }

    public static String createStringWithLength(int length) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < length; index++) {
            builder.append("a");
        }

        return builder.toString();
    }
}

試驗2:todo條目被添加到數據庫
第二,咱們要寫一個測試以確保控制器正常工做時,一個新的todo條目被添加到數據庫。咱們能夠這樣寫測試按如下步驟:

 一、經過使用測試數據生成器類建立一個對象。設置「合法」的值的名稱和建立對象的描述域。
 二、建立一個對象時返回作的todoservice接口add()方法稱爲。
 三、配置咱們的模擬對象建立對象時返回作的add()方法被調用和建立的窗體對象做爲方法參數。
 四、執行POST請求的URL /作/添加」。設置請求的應用程序/窗體-urlencoded內容類型」。確保內容的形式對象發送請求的身體。設置窗體對象爲會話。
 五、確認HTTP狀態碼返回302。
 六、確保返回的視圖名稱是「重定向:待辦事項/ {id}」。
 七、確保請求重定向到的URL /作/ 1′。
 八、驗證了模型的屬性稱爲ID是1′。
 九、確認反饋消息設置。
 十、驗證咱們的模擬對象的add()方法被稱爲只有一次,表單對象做爲方法參數。
 十一、請確認沒有其餘的模仿對象的方法,在咱們的測試要求。

咱們的單元測試的源代碼以下:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_NewTodoEntry_ShouldAddTodoEntryAndRenderViewTodoEntryView() throws Exception {
        TodoDTO formObject = new TodoDTOBuilder()
                .description("description")
                .title("title")
                .build();

        Todo added = new TodoBuilder()
                .id(1L)
                .description(formObject.getDescription())
                .title(formObject.getTitle())
                .build();

        when(todoServiceMock.add(formObject)).thenReturn(added);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isMovedTemporarily())
                .andExpect(view().name("redirect:todo/{id}"))
                .andExpect(redirectedUrl("/todo/1"))
                .andExpect(model().attribute("id", is("1")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));

        verify(todoServiceMock, times(1)).add(formObject);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

摘要
咱們如今已經有了「使用Spring MVC框架測試正常」控制器的方法寫一些單元測試。這篇教程已經教了四件事:
咱們學會了創造出了控制器的方法處理請求。
咱們學會了對寫斷言的測試控制器的方法返回。
咱們學會了如何寫入控制器的方法使一個視圖的單元測試。
咱們學會了寫控制器的方法處理表單提交的單元測試。
本教程的下一部分介紹瞭如何編寫單元測試REST API。
注:這篇文章的示例應用程序可在GitHub。我建議你檢查出來,由於它有一些單元測試是不包括在本博客。

 

 

http://java.dzone.com/articles/junit-testing-spring-mvc-1

相關文章
相關標籤/搜索