玩轉單元測試之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 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM