zoukankan      html  css  js  c++  java
  • 如何为 SpringMVC 编写单元测试:普通 Controller 测试(转)

    前一篇文章我们已经知道如何配置使用了 SpringMVC 测试框架的单元测试。

    现在我们就该亲身实践下如何为普通 Controller 编写单元测试了。

    接下来一个很明显的问题就是:

    什么是普通 Controller

    其实,就这篇文章来说普通 Controller 就是指负责渲染界面或处理请求的 Controller。

    如果你没读过前面的配置篇,那么我建议你先读一下。

    使用 Maven 获取必须依赖

    我们可以通过为我们的样例程序中的 POM 文件添加以下依赖声明来获取必须依赖:

    Jackson 2.2.1 (core 和 databind 模块)。我们使用 Jackson 把对象转化为字符串。 
    Hamcrest 1.3。使用它为返回内容写断言。 
    JUnit 4.11 (不需要包括 hamcrest-core 依赖). 
    Mockito 1.9.5 
    Spring Test 3.2.3.RELEASE 
    体现在 pom.xml 文件中的相关配置如下:

     <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-core</artifactId>
         <version>2.2.1</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.2.1</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.hamcrest</groupId>
         <artifactId>hamcrest-all</artifactId>
         <version>1.3</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.11</version>
         <scope>test</scope>
         <exclusions>
             <exclusion>
                 <artifactId>hamcrest-core</artifactId>
                 <groupId>org.hamcrest</groupId>
             </exclusion>
         </exclusions>
     </dependency>
     <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
         <version>1.9.5</version>
         <scope>test</scope>
     </dependency>
     <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>3.2.3.RELEASE</version>
        <scope>test</scope>
     </dependency>

    接下来让我们看看如何使用 SpringMVC 测试框架为普通 Controller 写单元测试。

    为 Controller 中方法编写单元测试

    我们为 Controller 中方法写的每一个测试都包括以下几个步骤:

    往 Controller 中方法发送一个请求。 
    验证返回的内容是不是符合预期。 
    SpringMVC 为我们更便捷的实现这几步提供了几个核心类。基本描述如下:

    我们可以使用 MockMvcRequestBuilders 类的静态方法构建请求对象。更确切的说, 我们可以新建一个请求构建对象并把它当作参数传递给请求方法。 
    MockMvc 类是整个测试的入口。我们可以调用它的 perform(RequestBuilder requestBuilder) 方法执行请求。 
    我们可以使用 MockMvcResultMatchers 类的表态方法为接收到的返回对象写断言。 
    接下来让我们看几个如何在测试中使用它们的例子。我们会如下几个方法编写单元测试:

    第一个 Controller 方法会为 Todo 对象渲染一个列表页。 
    第二个 Controller 方法是为单个 Todo 对象渲染详情页。 
    第三个 Controller 方法接收表单并在数据库中新建 Todo 对象记录。 
    渲染 Todo 对象列表页

    让我们先了解下渲染 Todo 对象列表页的代码。

    预期行为

    用来展示列表的方法实现主要包括以下几步:

    接收发送到 ‘/’ 地址的 GET 请求。 
    调用 TodoService 接口的 findAll() 方法获取全部 Todo 对象。这个方法会返回一个包含 Todo 对象的列表。 
    把接收到的列表添加到 Model 对象中。 
    返回待渲染视图名称。 
    相关的 TodoController 类中代码如下:

    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    import java.util.List;
    
    @Controller
    public class TodoController {
    
        private final TodoService service;
    
        @RequestMapping(value = "/", method = RequestMethod.GET)
        public String findAll(Model model) {
            List<Todo> models = service.findAll();
            model.addAttribute("todos", models);
            return "todo/list";
        }
    }

    现在我们可以开始为这个方法写单元测试了。

    测试: 查询到 Todo 对象列表时

    我们可以通过以下几步为这个 Controller 方法编写单元测试:

    创建 Service 中方法被调用时的返回数据。我们使用一个叫测试数据构建器的概念来表示创建测试数据。 
    将冒烟对象的 findAll() 方法被调用时的返回对象配置成前面创建的测试数据。 
    往 ‘/’ 地址发送一个 GET 请求。 
    确认返回的 HTTP 状态码是 200。 
    确认返回的视图名称是 ‘todo/list’。 
    确认请求被定向到地址 ‘/WEB-INF/jsp/todo/list.jsp’。 
    确认 Model 对象中的 todos 属性中有两个元素。 
    确认 Model 对象中的 todos 属性中的对象都是正确的。 
    确认冒烟对象的 findAll() 方法仅被调用过一次。 
    确认冒烟对象的其它方法在测试过程中没被调用过。 
    单元测试源代码如下:

    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 java.util.Arrays;
    
    import static org.hamcrest.Matchers.*;
    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.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
    @WebAppConfiguration
    public class TodoControllerTest {
    
        private MockMvc mockMvc;
    
        @Autowired
        private TodoService todoServiceMock;
    
        //此处添加 WebApplicationContext 字段
        //setUp() 方法
    
        @Test
        public void findAll_ShouldAddTodoEntriesToModelAndRenderTodoListView() throws Exception {
            Todo first = new TodoBuilder()
                    .id(1L)
                    .description("Lorem ipsum")
                    .title("Foo")
                    .build();
            Todo second = new TodoBuilder()
                    .id(2L)
                    .description("Lorem ipsum")
                    .title("Bar")
                    .build();
    
            when(todoServiceMock.findAll()).thenReturn(Arrays.asList(first, second));
    
            mockMvc.perform(get("/"))
                    .andExpect(status().isOk())
                    .andExpect(view().name("todo/list"))
                    .andExpect(forwardedUrl("/WEB-INF/jsp/todo/list.jsp"))
                    .andExpect(model().attribute("todos", hasSize(2)))
                    .andExpect(model().attribute("todos", hasItem(
                            allOf(
                                    hasProperty("id", is(1L)),
                                    hasProperty("description", is("Lorem ipsum")),
                                    hasProperty("title", is("Foo"))
                            )
                    )))
                    .andExpect(model().attribute("todos", hasItem(
                            allOf(
                                    hasProperty("id", is(2L)),
                                    hasProperty("description", is("Lorem ipsum")),
                                    hasProperty("title", is("Bar"))
                            )
                    )));
    
            verify(todoServiceMock, times(1)).findAll();
            verifyNoMoreInteractions(todoServiceMock);
        }
    }

    渲染 Todo 对象详情页

    在写具体测试代码前,让我们先看看待测方法的实现。

    预期行为

    用以展示单个 Todo 对象信息的 Controller 方法实现中主要包含以下几步:

    接收发送到 ‘/todo/{id}’ 地址的 GET 请求。{id} 是一个用于标识被请求 Todo 对象主键的地址变量。 
    它通过调用 TodoService 接口的 findById() 方法获取被请求的 Todo 对象,方法参数是被请求 Todo 对象的主键。这个方法会返回找到的 Todo 对象。如果没找到对应对象,这个方法会抛出 TodoNotFoundException 异常。 
    它会把找到的 Todo 对象添加到 Model 对象中。 
    返回待渲染的视图对象。 
    Controller 方法源代码如下:

     import org.springframework.stereotype.Controller;
     import org.springframework.ui.Model;
     import org.springframework.web.bind.annotation.*;
    
     @Controller
     public class TodoController {
    
         private final TodoService service;
    
         @RequestMapping(value = "/todo/{id}", method = RequestMethod.GET)
         public String findById(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
             Todo found = service.findById(id);
             model.addAttribute("todo", found);
             return "todo/view";
         }
     }

    下一个问题就是:

    什么时候抛出 TodoNotFoundException 异常?

    在本系列指南前面我们提到过,我们为 Controller 类抛出的异常创建过一个处理类。这个处理类的配置信息大体是这样的:

    @Bean
    public SimpleMappingExceptionResolver exceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
    
        Properties exceptionMappings = new Properties();
    
        exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
        exceptionMappings.put("java.lang.Exception", "error/error");
        exceptionMappings.put("java.lang.RuntimeException", "error/error");
    
        exceptionResolver.setExceptionMappings(exceptionMappings);
    
        Properties statusCodes = new Properties();
        statusCodes.put("error/404", "404");
        statusCodes.put("error/error", "500");
        exceptionResolver.setStatusCodes(statusCodes);
    
        return exceptionResolver;
    }

    正如我们看到的,如果抛出一个 TodoNotFoundException 异常,应用会返回 404 状态码并渲染 ‘error/404′ 视图。

    为这个 Controller 方法编写单元测试时很明显包括以下两步:

    必须有一个用例用来确保当找不到 Todo 对象时应用能正确执行。 
    必须有一个用例用来确保当 Todo 对象被找到时也能正确执行。 
    现在看看这个测试应该怎么写。

    测试 1: 查询不到 Todo 对象时

    首先,我们必须要确保我们的应用在找不到被查询 Todo 对象时也能正常工作。我们可以通过以下几步来进行测试:

    配置冒烟对象,让它在 findById() 方法以参数 1L 被调用时抛出 TodoNotFoundException 异常。 
    往 ‘/todo/1′ 地址发送一个 GET 请求。 
    确认返回的 HTTP 状态码是 404。 
    确保返回的视图名称是 ‘error/404′。 
    确保请求被定向到地址 ‘/WEB-INF/jsp/error/404.jsp’。 
    确认 TodoService 接口的 findById() 方法仅被调用过一次而且参数为 1L。 
    确认冒烟对象的其它方法在测试期间没被调用过。 
    这个测试用例的代码如下:

    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: 查询到 Todo 对象时

    接上文,现在我们需要写一个确保应用在能查询到 Todo 对象时也能正确工作的测试用例。基本步骤如下:

    创建 Service 中方法被调用时返回的 Todo 对象。同样,我们还是通过测试数据构建器创建测试对象。 
    配置冒烟对象,让它在 findById() 方法以 1L 参数被调用时返回前面创建的 Todo 对象。 
    往 ‘/todo/1′ 地址发送 GET 请求。 
    确认返回的 HTTP 状态码是 200。 
    确保返回的视图名称是 ‘todo/view’。 
    确保请求被定向到地址 ‘/WEB-INF/jsp/todo/view.jsp’。 
    确认 Model 对象中的 Todo 对象主键是 1L。 
    确认 Model 对象中的 Todo 对象描述字段值为 ‘Lorem ipsum’。 
    确认 Model 对象中的 Todo 对象名称字段值为 ‘Foo’。 
    确保冒烟对象的 findById() 方法仅被调用过一次,而且参数为 1L。 
    确保冒烟对象中的其它方法在测试过程中没被调用过。 
    测试用例代码如下:

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

    处理表单并在数据库中添加 Todo 记录

    首先,在编写测试用例前还是看一下待测试 Controller 方法的预期行为。

    预期行为

    负责处理表单并入库的 Controller 方法基本实现步骤如下:

    接收发送到 ‘/todo/add’ 地址的 POST 请求。 
    校验传递过来的 BingdingResult 参数是正确的,否则返回表单视图名称。 
    以表单对象为参数调用 TodoService 接口的 add() 方法进行 Todo 对象入库。 
    创建返回信息并将返回信息作为参数传递给 RedirectAttributes 中。 
    把入库的 Todo 对象主键添加到 RedirectAttributes 中。 
    把请求重定向到 Todo 对象详情页并渲染。 
    位于 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 类只是一个简单的数据传输类,它的代码大致如下:

    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.
    }

    这个类中声明了如下一些校验约束:

    Todo 对象中的 title 属性不能为空。 
    description 属性的最大长度是 500 字符。 
    title 属性的最大长度是 100 字符。 
    如果我们仔细思考下我们该为这个方法写的测试,就会发现我们至少有以下几点需要做的:

    在参数校验失败时 Controller 方法需要能正常工作。 
    校验通过时也能正常工作并正常入库。 
    现在让我们看看这个测试应该怎么写。

    测试 1: 校验失败时

    首先,我们需要测一下校验失败时 Controller 方法也能正常工作。这个测试我们可以这么干:

    创建 title 属性包含 101 字符。 
    创建 description 属性包含 501 字符。 
    通过以下几步往 ‘/todo/add’ 地址发送 POST 请求: 
    把请求的 Content-Type 设置成 ‘application/x-www-form-urlencoded’。 
    把前面提到的 title 和 description 当作请求参数发送过去。 
    在 session 中设置一个 TodoDTO 对象。这是必须的因为我们的 Controller 有一个 @SessionAttributes 注解。 
    校验返回的 HTTP 状态码是 200。 
    校验返回的视图名称是 ‘todo/add’。 
    校验请求被定向到地址 ‘/WEB-INF/jsp/todo/add.jsp’。 
    校验 model 对象中有 title 和 description 参数错误的提示信息。 
    确保 model 中的 id 属性是空。 
    确保 model 中的 description 属性是正确的。 
    确保 model 中的 title 属性是正确的。 
    确保冒烟对象中的方法在测试过程中没被调用过。 
    这个测试的源代码大体如下:

    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;
    
        //此处添加 WebApplicationContext 字段
        //setUp() 方法
    
        @Test
        public void add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() throws Exception {
            String title = TestUtil.createStringWithLength(101);
            String description = TestUtil.createStringWithLength(501);
    
            mockMvc.perform(post("/todo/add")
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .param("description", description)
                    .param("title", title)
                    .sessionAttr("todo", new TodoDTO())
            )
                    .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) 方法。这个方法会按照给定的长度创建并返回字符串。

    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 String createStringWithLength(int length) {
            StringBuilder builder = new StringBuilder();
            for (int index = 0; index < length; index++) {
                builder.append("a");
            }
            return builder.toString();
        }
    }

    测试 2: 校验通过且正常入库时

    现在,让我们测试可以正常入库时 Controller 能否正常工作。这个测试可以分以下几步:

    创建一个 Todo 对象,它会在 TodoService 接口的 add() 方法被调用时返回。 
    配置冒烟对象让它在 add() 方法按给定 TodoDTO 对象为参数调用时返回前面创建的 Todo 对象。 
    通过以下几步往 ‘/todo/add’ 地址发送 POST 请求: 
    把请求的 Content-Type 设置成 ‘application/x-www-form-urlencoded’。 
    把 Todo 对象的 description 和 title 字段当做参数发送请求。 
    在 session 中添加一个 TodoDTO 对象。因为我们的 Controller 类上包含一个 @SessionAttributes 注解。 
    确认返回的 HTTP 状态码是 302。 
    确认返回的视图名称是 ‘redirect:todo/{id}’。 
    确认请求被定向到地址 ‘/todo/1’。 
    确认 model 中 id 属性值为 ‘1’。 
    确认返回信息已正确设置。 
    确认冒烟对象的 add() 方法仅被调用过一次而且参数是 TodoDTO 对象。使用 ArgumentCaptor 为使用的参数做一个快照。 
    确认冒烟对象的其它方法在测试过程中没被调用过。 
    确认 TodoDTO 对象的和字段值是正确的。 
    这个单元测试的源代码如下:

    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.ArgumentCaptor;
    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.junit.Assert.assertNull;
    import static org.junit.Assert.assertThat;
    import static org.mockito.Matchers.isA;
    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;
    
        //此处添加 WebApplicationContext 字段
        //setUp() 方法
    
        @Test
        public void add_NewTodoEntry_ShouldAddTodoEntryAndRenderViewTodoEntryView() throws Exception {
            Todo added = new TodoBuilder()
                    .id(1L)
                    .description("description")
                    .title("title")
                    .build();
            when(todoServiceMock.add(isA(TodoDTO.class))).thenReturn(added);
    
            mockMvc.perform(post("/todo/add")
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .param("description", "description")
                    .param("title", "title")
                    .sessionAttr("todo", new TodoDTO())
            )
                    .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.")));
            ArgumentCaptor<TodoDTO> formObjectArgument = ArgumentCaptor.forClass(TodoDTO.class);
            verify(todoServiceMock, times(1)).add(formObjectArgument.capture());
            verifyNoMoreInteractions(todoServiceMock);
    
            TodoDTO formObject = formObjectArgument.getValue();
            assertThat(formObject.getDescription(), is("description"));
            assertNull(formObject.getId());
            assertThat(formObject.getTitle(), is("title"));
        }
    }

    总结

    我们现在已经使用 SpringMVC 测试框架写了好几个普通 Controller 的测试用例了。通过这篇指南我们学会了:

    如何为被测试 Controller 方法创建请求对象。 
    如何为被测试 Controller 方法的返回做断言。 
    如何为渲染视图的 Controller 写单元测试。 
    如何为处理表单的 Controller 写单元测试。 
    本系列指南接下来的部分将会告诉我们如何为 REST API 写单元测试。

    转载注明出处:如何为 SpringMVC 编写单元测试:普通 Controller 测试

    原文链接:http://lzxz1234.github.io/junit/2014/07/10/Unit-Testing-of-Spring-MVC-Controllers-Normal-Controller/

  • 相关阅读:
    Ubuntu 18.04.4 系统优化
    Ubuntu 18.04.4 LTS 更换国内系统源
    django 数据库迁移
    django2.0解决跨域问题
    python requests get请求传参
    python 常用排序方法
    python 电脑说话
    centos6.x配置虚拟主机名及域名hosts
    php 合并,拆分,追加,查找,删除数组教程
    PHP统计在线用户数量
  • 原文地址:https://www.cnblogs.com/zhangwufei/p/7602310.html
Copyright © 2011-2022 走看看