Spring MVC使用MockMvc和Junit进行单元测试

  声明一点:对于软件质量的保证,单元测试真的非常有必要,在CMMI-5标准也是对单元测试有明确要求的。

  有的公司的对开发要求严谨的话,开发流程可能执行的是测试驱动开发(TDD),即根据拿到需求,开发人员先写预期单元测试用例,再根据满足测试用例的要求来开发业务代码。

  在不启动项目的情况下,对Spring Web项目,需要一些Servlet相关的模拟对象,比如:MockMvc,MockHttpSession,MockHttpServletRequest,MockHttpServletResponse等,还需要WebAppConfiguration

示例代码

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Sprign test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.service.impl.DemoServiceImpl;

@Controller
public class NormalAndRestController {

@Autowired
DemoServiceImpl demoServiceImpl;

@RequestMapping("/normal")
public String testPage(Model model) {
model.addAttribute("msg", demoServiceImpl.saySomething());
return "page";
}

@RequestMapping(value = "/testRest", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String testRest() {
return demoServiceImpl.saySomething();
}
}

Service

1
2
3
4
5
6
7
8
9
10
import org.springframework.stereotype.Service;

@Service
public class DemoServiceImpl {

public String saySomething() {
return "Hello World";
}
}

单元测试代码

Maven管理的Spring项目,单元测试代码放在src/test/java目录里

  1. @RunWith(SpringJUnit4ClassRunner.class):让测试运行于 Spring-test 测试环境。
  2. @WebAppConfiguration:指定加载的ApplicationContext是个WebAppConfiguration
  3. @ContextConfiguration:加载配置类或XML配置文件。
  4. @Transactional:用于事务回滚,将测试数据删除掉,保证数据库数据干净。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.mock.web.MockHttpServletResponse;
    import org.springframework.mock.web.MockHttpSession;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
    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 com.config.SpringMvcConfig;
    import com.service.impl.DemoServiceImpl;

    /**
    * Mockmvc测试数据回滚:
    * 1. 继承AbstractTransactionalJUnit4SpringContextTests类是实现数据自动回滚
    * 2. 使用@Transactional
    * 3. 使用@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
    * @author Rocky
    *
    */

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringMvcConfig.class)
    @WebAppConfiguration("/src/main/webapp")//声明加载WebApplicationContext
    public class TestController extends AbstractTransactionalJUnit4SpringContextTests {

    private MockMvc mockMvc;

    @Autowired
    private DemoServiceImpl demoServiceImpl;//在测试代码中注入Spring的Bean

    @Autowired
    WebApplicationContext webApplicationContext; //注入WebApplicationContext

    @Autowired
    MockHttpSession session;//注入模拟http session

    @Autowired
    MockHttpServletRequest request;//注入模拟http request

    @Autowired
    MockHttpServletResponse response; //注入模拟http response

    /**
    * 在测试开始前初始化
    */
    @Before
    public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
    }


    /**
    * 向normal进行get请求,预控制返回200
    * 预期view名为page
    * @throws Exception
    */

    @Test
    public void testNormalController() throws Exception {
    mockMvc.perform(get("/normal"))
    .andExpect(status().isOk())
    .andExpect(view().name("page"))
    .andExpect(forwardedUrl("/WEB-INF/jsp/page.jsp"))
    .andExpect(model().attribute("msg", demoServiceImpl.saySomething()));
    }

    @Test
    public void testRestController() throws Exception {
    mockMvc.perform(get("/testRest"))
    .andExpect(status().isOk())
    .andExpect(content().contentType("text/plain;charset=UTF-8"))
    .andExpect(content().string(demoServiceImpl.saySomething()));
    }
    }

    page.jsp页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <%@ page language="java" contentType="text/html; charset=UTF-8"	pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.11.0.js"></script>

    <title>Test Page</title>
    </head>
    <body>
    <pre>Welcome to Spring MVC World</pre>
    </body>
    </html>

    MockMvc原理分析

    MockMvc执行测试步骤:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Test
    public void testNormalController() throws Exception {
    //链式编程,需导入静态包
    mockMvc.perform(get("/normal"))
    .andExpect(status().isOk())
    .andExpect(view().name("page"))
    .andExpect(forwardedUrl("/WEB-INF/jsp/page.jsp"))
    .andExpect(model().attribute("msg", demoServiceImpl.saySomething()));

    //1. 构造一个请求
    RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/normal");
    //2. 执行请求
    ResultActions resultActions = mockMvc.perform(requestBuilder);

    //3. 结果匹配
    ResultMatcher isOK = MockMvcResultMatchers.status().isOk();//返回的状态
    ResultMatcher viewName = MockMvcResultMatchers.view().name("page");//view name
    ResultMatcher viewURL = MockMvcResultMatchers.forwardedUrl("/WEB-INF/jsp/page.jsp");//物理视图
    ResultMatcher attribute = MockMvcResultMatchers.model().attribute("msg", demoServiceImpl.saySomething());//封装参数

    //4. 执行完成后的断言
    resultActions.andExpect(isOK)
    .andExpect(viewName)
    .andExpect(viewURL)
    .andExpect(attribute);
    }
  5. 测试之前执行初始化
    MockMvcBuilder 是用来构造 MockMvc 的构造器,其主要有两个实现:StandaloneMockMvcBuilderDefaultMockMvcBuilderStandaloneMockMvcBuilder 继承了 DefaultMockMvcBuilder。 直接使用静态工厂 MockMvcBuilders 创建即可。 **MockMvcBuilders.webAppContextSetup(WebApplicationContext context)**:指定 WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的 MockMvc

  原理分析: MockMvcBuilders 构造器是重点,webAppContextSetup()方法是通过DefaultMockMvcBuilder构造方法返回一个包含WebApplicationContext的对象。

1
2
3
public static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context) {
return new DefaultMockMvcBuilder(context);
}

  DefaultMockMvcBuilder继承了AbstractMockMvcBuilder,提供了一API
- **addFilters(Filter… filters)/addFilter(Filter filter, String… urlPatterns)**:添加javax.servlet.Filter过滤器。
- **defaultRequest(RequestBuilder requestBuilder)**:默认的 RequestBuilder,每次执行时会合并到自定义的 RequestBuilder中,即提供公共请求数据的。
- **alwaysExpect(ResultMatcher resultMatcher)**:定义全局的结果验证器,即每次执行请求时都进行验证的规则。
- **alwaysDo(ResultHandler resultHandler)**:定义全局结果处理器,即每次请求时都进行结果处理。
- dispatchOptionsDispatcherServlet 是否分发 OPTIONS 请求方法到控制器。

  1. 其它见上方代码注释:2,3,4。
  2. ResultActions.andDo 添加一个结果处理器,表示要对结果做点什么事情,比如此处使用 MockMvcResultHandlers.print() 输出整个响应结果信息。
  3. ResultActions.andReturn 表示执行完成后返回相应的结果。

Spring MVC使用MockMvc和Junit进行单元测试

http://blog.gxitsky.com/2018/02/24/SpringMVC-34-test-spring-mockmv/

作者

光星

发布于

2018-02-24

更新于

2022-06-17

许可协议

评论