Unit Testing of Spring MVC
试验1:做的条目不发现
首先,我们必须确保我们的应用是工作性质所做条目不发现。我们可以写的测试以确保通过以下步骤:
1、配置的模拟对象时抛出一个todonotfoundexception findbyid()方法被调用和请求的待办事项条目ID 1L。
2、执行一个GET请求的URL /做/ 1′。
3、确认HTTP状态码返回404。
4、确保返回的视图名称是“错误/ 404′。
5、确保请求转发到URL“/WEB-INF/JSP /错误/ 404 JSP”。
6、验证的todoservice接口findbyid()方法被称为只有一次正确的方法参数(1)。
7、请确认没有其他的模仿对象的方法都是在这个测试被称为。
我们的单元测试的源代码如下:
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 {
private MockMvc mockMvc;
@Autowired
private TodoService todoServiceMock;
//Add WebApplicationContext field here
//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:做条目被发现
第二,我们必须写一个测试以确保控制器正常工作时,全部的入口被发现。我们可以通过下面的步骤:
1、创建待办事项的对象是回来时,我们的服务调用的方法。再次,我们创建返回的Todo对象通过使用我们的测试数据生成器。
2、配置我们的模拟对象创建对象时返回做的findbyid()方法是采用参数1。
3、执行一个GET请求的URL /做/ 1′。
4、确认HTTP状态码返回200。
5、确保返回的视图名称是“做/视图”。
6、确保请求转发到URL“/WEB-INF/JSP /做/视图JSP”。
7、验证的模型被称为“对象ID 1L。
8、验证的模型对象,被称为“的描述是“lorem ipsum”。
9、验证的模型对象,被称为“标题是“foo”。
10、确保我们的模拟对象的findbyid()方法被称为只有一次正确的方法参数(1)。
11、确保模拟对象的其他方法不在我们的测试要求。
我们的单元测试的源代码如下:
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);
}
}
在添加待办事项表格表单提交
再次,我们将首先在我们会为它编写单元测试将在我们的控制方法的预期行为一看。
预期的行为
该控制器的方法处理的添加待办事项报名表是通过以下这些步骤的形式提交:
1、它处理POST请求发送到URL /做/添加”。
2、它会检查作为方法的参数不该BindingResult物体有任何错误。如果发现错误,则返回窗体视图的名称。
3、它增加了通过调用接口的todoservice add()方法的一个新的待办事项的进入和通过表单对象作为方法参数。该方法创建一个新的待办事项条目并返回它。
4、它创造了对添加待办事项输入反馈的消息,将消息添加到redirectattributes对象作为方法参数。
5、它增加了额外的任务进入redirectattributes对象ID。
6、它返回一个重定向视图将请求重定向到登录页面查看待办事项名称。
的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类声明了一些验证约束如下:
1、一项不做标题是空的。
2、描述的最大长度是500个字符。
3、标题的最大长度是100个字符。
如果我们想测试,我们应该为这个控制器的方法写,它是明确的,我们必须确保
1、该控制器的方法是工作性质当验证失败。
2、该控制器的方法是工作性质当todo条目被添加到数据库。
让我们看看如何写这些测试。
试验1:验证失败
首先,我们要写一个测试,确保我们的控制器的方法是正常工作,当验证失败。我们可以这样写测试按以下步骤:
1、创建一个标题,101字。
2、创建一个描述这501个字符。
3、通过使用我们的测试数据生成器创建一个新的tododto对象。设置标题和对象的描述。
4、执行POST请求的URL /做/添加”。设置请求的应用程序/窗体-urlencoded内容类型”。确保内容的形式对象发送请求的身体。设置窗体对象为会话。
5、确认HTTP状态码返回200。
6、确认返回的视图名称是“做/添加”。
7、验证请求转发到URL“/WEB-INF/JSP /做/添加JSP”。
8、验证我们的模型的属性是场错误的标题和描述领域。
9、确保我们的模型属性ID无效。
10、确保我们的模型属性的描述是正确的。
11、确保我们的模型属性的标题是正确的。
12、确保我们的模拟对象方法不在测试过程中被称为。
我们的单元测试的源代码如下:
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类调用静态方法。这些方法如下:
1、该createstringwithlength(int length)方法创建一个新的具有给定长度的字符串对象并返回创建的对象。
2、该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条目被添加到数据库。我们可以这样写测试按以下步骤:
1、通过使用测试数据生成器类创建一个对象。设置“合法”的值的名称和创建对象的描述域。
2、创建一个对象时返回做的todoservice接口add()方法称为。
3、配置我们的模拟对象创建对象时返回做的add()方法被调用和创建的窗体对象作为方法参数。
4、执行POST请求的URL /做/添加”。设置请求的应用程序/窗体-urlencoded内容类型”。确保内容的形式对象发送请求的身体。设置窗体对象为会话。
5、确认HTTP状态码返回302。
6、确保返回的视图名称是“重定向:待办事项/ {id}”。
7、确保请求重定向到的URL /做/ 1′。
8、验证了模型的属性称为ID是1′。
9、确认反馈消息设置。
10、验证我们的模拟对象的add()方法被称为只有一次,表单对象作为方法参数。
11、请确认没有其他的模仿对象的方法,在我们的测试要求。
我们的单元测试的源代码如下:
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