玩转单元测试之 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

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

  1. Unit Testing of Spring MVC Controllers: “Normal” Controllers

    Original link: http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-m ...

  2. Unit Testing of Spring MVC Controllers: Configuration

    Original Link: http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-m ...

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

    就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/43 ...

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

    接我前面一篇文章关于RestAssured测试Restful web service的, RestAssured还有一个功能, 使用RestAssuredMockMvc 单元测试你的Spring MV ...

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

    玩转单元测试之WireMock -- Web服务模拟器 WireMock 是一个灵活的库用于 Web 服务测试,和其他测试工具不同的是,WireMock 创建一个实际的 HTTP服务器来运行你的 We ...

  6. 玩转单元测试之DBUnit

    DBunit 是一种扩展于JUnit的数据库驱动测试框架,它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果. 虽然不 ...

  7. 单元测试之获取Spring下所有Bean

    单元测试中,针对接口的测试是必须的,但是如何非常方便的获取Spring注册的Bean呢? 如果可以获取所有的Bean,这样就可以将这个方法放到基类中,方便后面所有单元测试类的使用,具体实现如下: im ...

  8. Spring MVC Test -Controller

    http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers- ...

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

    整个spring mvc的架构如下图所示: 上篇文件讲解了DispatcherServlet通过request获取控制器Controller的过程,现在来讲解DispatcherServletDisp ...

随机推荐

  1. JavaWeb chapeter 5 Web应用程序状态管理

    1.  HTTP协议使用的是无状态连接,对容器而言,每一个请求都来自于一个新的客户. 2. html表单隐藏字段:对用户在网站上的访问进行会话跟踪.为服务器端程序提供预定义的输入.存储动态产生的页面上 ...

  2. ajax转换成json参数

    //提交表单 $('#submit').click(function(){ var datas = $("#iform").serializeJson(); datas.actio ...

  3. jsp与php混用的漏洞

    接手一个项目是jsp写的,用起来感觉开发是在太麻烦了.于是新功能就用php写的,jsp.php两者之间相互跳转, 突然一天发现在tomcat的web站下打开php竟然显示了php源码,在php站下看j ...

  4. SAP常用命令及BASIS操作

    Pfcg         角色,权限参数文件配置Su53        查看权限对象  st01  跟踪St22         看dump,以分析错误  eg.找到ABAP程序出错的地方,找出fou ...

  5. TreeSet和Comparator 对TreeSet排序

    使用TreeSet和Comparator,编写TreeSetTestInner类,要求对TreeSet中的元素"HashSet"."ArrayList".&qu ...

  6. 实现用CSS切割图片的方法

    切割图片这里不是真正的切割,只是用CSS取图片中的一部分而已.这样做的好处就是减少了打开网页时请求图片的次数.主要有两种方式,一是做为某一元素的背景图片,二是用img元素的属性. 方法一: 用CSS中 ...

  7. [转]Web3.0时代,企业知识管理新趋势

    [转自http://www.amt.com.cn/html/ManageFront/AMTPoint0/2014/0716/1370.html] Web3.0时代,企业知识管理新趋势 2014-07- ...

  8. 关于jquery中的事件绑定bind()和live()

    live可以说是bind是方法的变种. 二者的主要区别就是live方法的作用机理是事件委托,live方法的作用机理是将事件绑定DOM的根节点上. live方法的处理机制就是把事件绑定在DOM树的根节点 ...

  9. Android请求网络权限

    1,新建一个项目,在AndroidManiifest中添加 <uses-permission android:name="android.permission.INTERNET&quo ...

  10. Android ContentResolve使用

    在Android中使用ContentResolve访问其他程序的数据: http://developer.android.com/reference/android/content/ContentPr ...