【转】玩转单元测试之 Testing Spring MVC Controllers

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代码, 本文以两个例子讲述了服务端的测试,闲话少说,让我们边看例子边学习。

【转】玩转单元测试之 Testing Spring MVC Controllers
目录
  Getting Ready
  Example
     Reference Class
     Unit Test
     Integration Testing
     总结
  Troubleshooting
  参考
【转】玩转单元测试之 Testing Spring MVC Controllers

Getting Ready

测试相关Maven dependency如下:

【转】玩转单元测试之 Testing Spring MVC Controllers
<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>

【转】玩转单元测试之 Testing Spring MVC Controllers

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

Example

Reference Class

Controller 如下:

【转】玩转单元测试之 Testing Spring MVC Controllers
@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;
    }

}
【转】玩转单元测试之 Testing Spring MVC Controllers

FieldComparisonFailure类如下

【转】玩转单元测试之 Testing Spring MVC Controllers
/**
 * 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;
    }
}
【转】玩转单元测试之 Testing Spring MVC Controllers

TestProjectService接口如下:

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

}

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

【转】玩转单元测试之 Testing Spring MVC Controllers
@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;
    }

}
【转】玩转单元测试之 Testing Spring MVC Controllers

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

Unit Test

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

【转】玩转单元测试之 Testing Spring MVC Controllers
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);
    }
}
【转】玩转单元测试之 Testing Spring MVC Controllers

@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/

运行

【转】玩转单元测试之 Testing Spring MVC Controllers

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

【转】玩转单元测试之 Testing Spring MVC Controllers
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 = []
【转】玩转单元测试之 Testing Spring MVC Controllers

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

Integration Testing

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

【转】玩转单元测试之 Testing Spring MVC Controllers
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"));
    }
}
【转】玩转单元测试之 Testing Spring MVC Controllers

@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取得返回值。

运行时间比较长

【转】玩转单元测试之 Testing Spring MVC Controllers

控制台信息

【转】玩转单元测试之 Testing Spring MVC Controllers
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 = []
【转】玩转单元测试之 Testing Spring MVC Controllers

从上面的例子来看集成测试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

 

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

相关推荐