玩转单元测试之Testing Spring MVC Controllers

玩转单元测试之 Testing Spring MVC Controllers

转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html

The Spring MVC Test framework provides first class JUnit support for testing client and server-side Spring MVC code through a fluent API. Typically it loads the actual Spring configuration through theTestContext framework and always uses the DispatcherServlet to process requests thus approximating full integration tests without requiring a running Servlet container.

Spring MVC 测试框架本来是一个独立的项目,由于发展的很好,早已合并到Spring Framework 3.2 里了,测试框架提供了很好的API来测试客户端和服务端的Spring MVC代码, 本文以两个例子讲述了服务端的测试,闲话少说,让我们边看例子边学习。

目录
  Getting Ready
  Example
     Reference Class
     Unit Test
     Integration Testing
     总结
  Troubleshooting
  参考

Getting Ready

测试相关Maven dependency如下:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.0.3.RELEASE</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.9.5</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

<dependency>
          <groupId>org.hamcrest</groupId>
          <artifactId>hamcrest-core</artifactId>
          <version>1.3</version>
          <scope>test</scope>
      </dependency>

关于Spring项目的一些依赖如(spring-context, spring-web, spring-webmvc, spring-beans),这里就不列举了

Example

Reference Class

Controller 如下:

@Controller
@RequestMapping("/")
public class DemoController {

    @Autowired
    private TestProjectService testProjectService;

    @RequestMapping(value = "jsonCompare", method = RequestMethod.POST)
    @ResponseBody
    public List<FieldComparisonFailure> jsonCompare(@RequestParam("expect") String expect, @RequestParam("actual") String actual, ModelMap model,
            HttpSession session) {

        List<FieldComparisonFailure> list = testProjectService.compare(expect, actual);

        return list;
    }

}

FieldComparisonFailure类如下

/**
 * Models a failure when comparing two fields.
 */
public class FieldComparisonFailure {
    private final String field;
    private final Object expected;
    private final Object actual;

    public FieldComparisonFailure(String field, Object expected, Object actual) {
        this.field = field;
        this.expected = expected;
        this.actual = actual;
    }

    public String getField() {
        return field;
    }

    public Object getExpected() {
        return expected;
    }

    public Object getActual() {
        return actual;
    }
}

TestProjectService接口如下:

public interface TestProjectService {
    public List<FieldComparisonFailure> compare(String expect, String actual);

}

TestProjectServiceImpl 具体实现是比较两个Json字符串 返回一个List

@Service
public class TestProjectServiceImpl implements TestProjectService {

    @Override
    public List<FieldComparisonFailure> compare(String expect, String actual) {

        Comparator comparator = new Comparator();
        List<FieldComparisonFailure> list = new ArrayList<FieldComparisonFailure>();

        try {
            list = comparator.compare(expect, actual);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return list;
    }

}

##转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html

Unit Test

首先来看一个独立的单元测试方式, 这个例子用Mockito 模拟service层以便隔离controller的测试。

package com.wadeshop.controller;

import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.hamcrest.Matchers.hasSize;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.google.common.collect.ImmutableList;
import com.wadeshop.service.TestProjectService;
import com.wadeshop.entity.FieldComparisonFailure;

public class DemoControllerTest_mock {

    @Mock
    private TestProjectService testProjectService;

    @InjectMocks
    private DemoController demoController;

    private MockMvc mockMvc;

    @Before
    public void setup() {

        // initialize mock object
        MockitoAnnotations.initMocks(this);

        // Setup Spring test in standalone mode
        this.mockMvc = MockMvcBuilders.standaloneSetup(demoController).build();
    }

    @Test
    public void test() throws Exception {

        //prepare test data
        FieldComparisonFailure e1 = new FieldComparisonFailure("Number", "3", "4");
        FieldComparisonFailure e2 = new FieldComparisonFailure("Number", "1", "2");

        //actually parameter haven‘t use, service was mocked
        String expect = "";
        String actual = "";

        //Sets a return value to be returned when the method is called
        when(testProjectService.compare(expect, actual)).thenReturn(ImmutableList.of(e1, e2));

        //construct http request and expect response
       this.mockMvc
            .perform(post("/jsonCompare")
                     .accept(MediaType.APPLICATION_JSON)
               .param("actual", actual)
               .param("expect", expect))
               .andDo(print()) //print request and response to Console
               .andExpect(status().isOk())
               .andExpect(content().contentType("application/json;charset=UTF-8"))
               .andExpect(jsonPath("$", hasSize(2)))
               .andExpect(jsonPath("$[0].field").value("Number"))
               .andExpect(jsonPath("$[0].expected").value("3"))
               .andExpect(jsonPath("$[0].actual").value("4"))
               .andExpect(jsonPath("$[1].field").value("Number"))
               .andExpect(jsonPath("$[1].expected").value("1"))
               .andExpect(jsonPath("$[1].actual").value("2"));

         //verify Interactions with any mock
         verify(testProjectService, times(1)).compare(expect, actual);
         verifyNoMoreInteractions(testProjectService);
    }
}

@Mock: 需要被Mock的对象

@InjectMocks: 需要将Mock对象注入的对象, 此处就是Controller

Before test

初始化Mock对象, 通过MockMvcBuilders.standaloneSetup模拟一个Mvc测试环境,注入controller, 通过build得到一个MockMvc, 后面就用MockMvc的一些API做测试。

这不是真实的Spring MVC环境,如果要模拟真实环境需要用 MockMvcBuilders.webAppContextSetup(webApplicationContext).build(), 见下文。

测试方法里面需要构建测试数据,mock service调用方法,返回一个ImmutableList (google-collections 谷歌的集合库)

然后构造http请求并且传入参数执行, 最后断言验证期望结果, 关于JsonPath的使用请参考http://goessner.net/articles/JsonPath/

运行

andDo(print()) 打印到控制台的信息如下

MockHttpServletRequest:
         HTTP Method = POST
         Request URI = /jsonCompare
          Parameters = {actual=[], expect=[]}
             Headers = {Accept=[application/json]}

             Handler:
                Type = com.wadeshop.controller.DemoController

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[application/json;charset=UTF-8]}
        Content type = application/json;charset=UTF-8
                Body = [{"field":"Number","actual":"4","expected":"3"},{"field":"Number","actual":"2","expected":"1"}]
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

##转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html

Integration Testing

再来看集成Web环境方式, 这次仍然使用Spring MVC Test 但还需要加载 WebApplicationContext

import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration(value = "src/main/webapp")
@ContextConfiguration("file:src/main/resources/applicationContext.xml")

public class DemoControllerTest {

    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void test() throws Exception {
        String actual = "{\"orderNumber\": \"4955\",\"orderVersion\": \"1\"}";
        String expect = "{\"orderNumber\": \"4956\",\"orderVersion\": \"1\"}";

       this.mockMvc
            .perform(post("/jsonCompare")
                     .accept(MediaType.APPLICATION_JSON)
               .param("actual", actual)
               .param("expect", expect))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().contentType("application/json"))
               .andExpect(jsonPath("$", hasSize(1)))
               .andExpect(jsonPath("$[0].field").value("orderNumber"))
               .andExpect(jsonPath("$[0].actual").value("4955"))
               .andExpect(jsonPath("$[0].expected").value("4956"));
    }
}

@RunWith: 告诉Junit使用 Spring-Test 框架, 允许加载web 应用程序上下文。

@WebAppConfiguration: 表明该类会使用web应用程序的默认根目录来载入ApplicationContext, value = "src/main/webapp" 可以不填,默认此目录

@ContextConfiguration: 指定需要加载的spring配置文件的地址 ("file:src/main/resources/applicationContext.xml")

@Autowired WebApplicationContext wac:注入web环境的ApplicationContext容器;

使用MockMvcBuilders.webAppContextSetup(wac).build()来创建一个MockMvc进行测试, 此为模拟真实的Spring MVC环境

测试过程和前面一个例子大体相似,唯一的区别就是,这次传入的是真实的参数,调用真实的service取得返回值。

运行时间比较长

控制台信息

MockHttpServletRequest:
         HTTP Method = POST
         Request URI = /jsonCompare
          Parameters = {actual=[{"orderNumber": "4955","orderVersion": "1"}], expect=[{"orderNumber": "4956","orderVersion": "1"}]}
             Headers = {Accept=[application/json]}

             Handler:
                Type = com.wadeshop.controller.DemoController

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[application/json]}
        Content type = application/json
                Body = [{"field":"orderNumber","actual":"4955","expected":"4956"}]
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

从上面的例子来看集成测试Spring MVC controller是不是也很简单, 稍微配置一下就行了。

##转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html

总结

单元测试过程无非就这三部曲:

  1. 准备 (测试环境,测试数据)
  2. 执行 (构造请求传入参数执行)
  3. 断言 (验证结果)

Troubleshooting

如果发现一些NoClassDefFoundError, 估计依赖的jar版本太旧了。

import 哪些类不要弄错了,有些需要static import, IDE工具不一定会提示导入

参考

官方文档:http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/htmlsingle/#spring-mvc-test-framework

MVC测试框架更多的API请参考这篇博客:http://jinnianshilongnian.iteye.com/blog/2004660

感谢阅读,如果您觉得本文的内容对您的学习有所帮助,您可以点击右下方的推荐按钮,您的鼓励是我创作的动力。

##转载注明出处: http://www.cnblogs.com/wade-xu/p/4299710.html

时间: 2024-07-28 15:55:58

玩转单元测试之Testing Spring MVC Controllers的相关文章

就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers

就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/4311205.html 接我前面一篇文章关于RestAssured测试Restful web service的, RestAssured还有一个功能, 使用RestAssuredMockMvc 单元测试你的Spring MVC Controllers, 这个MockMvc 是建立在Spring Moc

就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers(转)

接我前面一篇文章关于RestAssured测试Restful web service的, RestAssured还有一个功能, 使用RestAssuredMockMvc 单元测试你的Spring MVC Controllers, 这个MockMvc 是建立在Spring MockMvc基础上的, 其目的是让我们用起来更便捷. Getting Ready <dependency> <groupId>com.jayway.restassured</groupId> <

玩转单元测试之WireMock -- Web服务模拟器

玩转单元测试之WireMock -- Web服务模拟器 WireMock 是一个灵活的库用于 Web 服务测试,和其他测试工具不同的是,WireMock 创建一个实际的 HTTP服务器来运行你的 Web 服务以方便测试. 它支持 HTTP 响应存根.请求验证.代理/拦截.记录和回放, 并且可以在单元测试下使用或者部署到测试环境. 它可以用在哪些场景下: 测试移动应用依赖于第三方REST APIs 创建快速原型的APIs 注入否则难于模拟第三方服务中的错误 任何单元测试的代码依赖于web服务的 目

玩转单元测试之DBUnit

DBunit 是一种扩展于JUnit的数据库驱动测试框架,它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果. 虽然不是什么新鲜货,但最近正好用到,就把学到的跟大家分享一下. 关键词:数据库层测试,DAO层测试,DBUnit教程,DBUnit入门,DBUnit实例,Sring中结合DBUnit对Dao层测试 目录 简介 前提条件 Maven配置 准备工作 实例详解 测试基类 关于数据集 Example 1 Flat

Spring MVC Test -Controller

http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers-configuration/ Writing unit tests for Spring MVC controllers has traditionally been both simple and problematic. Although it is pretty simple to write

spring mvc DispatcherServlet详解之二---request通过Controller获取ModelAndView

整个spring mvc的架构如下图所示: 上篇文件讲解了DispatcherServlet通过request获取控制器Controller的过程,现在来讲解DispatcherServletDispatcherServlet的第二步:通过request从Controller获取ModelAndView. DispatcherServlet调用Controller的过程: DispatcherServlet.java doService()--->doDispatch()--->handler

Spring与Web框架(例如Spring MVC)漫谈——关于Spring对于多个Web框架的支持

在看Spring MVC的官方文档时,最后一章是关于Spring对于其它Web框架的支持(如JSF,Apache Struts 2.x,Tapestry 5.x),当然Spring自己的MVC框架Spring MVC就不用多说了. 这里并不想讨论其它的Web框架,而是记录下这章开头提到的关于Spring为何还要支持其它Web框架. Spring Framwork的一个核心价值观是:允许开发者自由选择. 一般而言,Spring并不会强迫你使用或者购买某些特别的架构.技术或者方案,尽管它们肯定会特别

玩转Spring MVC(二)----MVC框架

早期程序员在编写代码时是没有一个规范的,系统的业务逻辑和数据库的访问时混合在一起的,不仅增加了大量的重复工作,而且增加了后期维护的难度. 后来,人们逐渐发现某些通用的功能是可以单独的封装起来的,这样就可以减少代码量. 再后来,逐渐产生了三层架构模型,即表现层,业务逻辑层,数据层.表现层即系统的界面,控制系统展示给用户的一些东西:数据层主要实现与数据库的交互,比如数据的增删改查:业务逻辑层处于数据访问层与表示层中间,起到了数据交换中承上启下的作用,主要用于加工处理数据.通过分层,削弱了个功能层之间

玩转spring MVC(七)----拦截器

继续在前边的基础上来学习spring MVC中拦截器的使用,下面通过一个例子来实现(完整项目在这里下载:http://download.csdn.net/detail/u012116457/8433425). 首先在项目中添加interceptor-servlet.xml来配置拦截器,当然,必须在web.xml中配置在tomcat启动时加载,如下: <!--1.配置spring分发器(是总的控制中心 被拦截的url会汇聚到该servlet) --> <servlet> <se