1. 程式人生 > >Junit測試Controller(MockMVC使用),傳輸@RequestBody資料解決辦法

Junit測試Controller(MockMVC使用),傳輸@RequestBody資料解決辦法

一、單元測試的目的

  簡單來說就是在我們增加或者改動一些程式碼以後對所有邏輯的一個檢測,尤其是在我們後期修改後(不論是增加新功能,修改bug),都可以做到重新測試的工作。以減少我們在釋出的時候出現更過甚至是出現之前解決了的問題再次重現。

  這裡主要是使用MockMvc對我們的系統的Controller進行單元測試。

  對資料庫的操作使用事務實現回滾,及對資料庫的增刪改方法結束後將會還遠資料庫。

二、MockMvc的使用

1、首先我們上一個例子,

複製程式碼

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
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.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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.status;

/**
 * Created by zhengcanrui on 16/8/11.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/applicationContext-*xml"})

//配置事務的回滾,對資料庫的增刪改都會回滾,便於測試用例的迴圈利用
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional

@WebAppConfiguration
public class Test {
    //記得配置log4j.properties ,的命令列輸出水平是debug
    protected Log logger= LogFactory.getLog(TestBase.class);

    protected MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext wac;

    @Before()  //這個方法在每個方法執行之前都會執行一遍
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();  //初始化MockMvc物件
    }

    @org.junit.Test
    public void getAllCategoryTest() throws Exception {
        String responseString = mockMvc.perform(
                get("/categories/getAllCategory")    //請求的url,請求的方法是get
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)  //資料的格式
               .param("pcode","root")         //新增引數
        ).andExpect(status().isOk())    //返回的狀態是200
                .andDo(print())         //打印出請求和相應的內容
                .andReturn().getResponse().getContentAsString();   //將相應的資料轉換為字串
        System.out.println("--------返回的json = " + responseString);
    }

}

複製程式碼

  Spring MVC的測試往往看似比較複雜。其實他的不同在於,他需要一個ServletContext來模擬我們的請求和響應。但是Spring也針對Spring MVC 提供了請求和響應的模擬測試介面,以方便我們的單元測試覆蓋面不只是service,dao層。

2、程式碼解釋:

@webappconfiguration是一級註釋,用於宣告一個ApplicationContext整合測試載入WebApplicationContext。作用是模擬ServletContext

@ContextConfiguration:因為controller,component等都是使用註解,需要註解指定spring的配置檔案,掃描相應的配置,將類初始化等。

@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional

上面兩句的作用是,讓我們對資料庫的操作會事務回滾,如對資料庫的新增操作,在方法結束之後,會撤銷我們對資料庫的操作。

為什麼要事務回滾?

  • 測試過程對資料庫的操作,會產生髒資料,影響我們資料的正確性
  • 不方便迴圈測試,即假如這次我們將一個記錄刪除了,下次就無法再進行這個Junit測試了,因為該記錄已經刪除,將會報錯。
  • 如果不使用事務回滾,我們需要在程式碼中顯式的對我們的增刪改資料庫操作進行恢復,將多很多和測試無關的程式碼 

方法解析:

  • perform執行一個RequestBuilder請求,會自動執行SpringMVC的流程並對映到相應的控制器執行處理;
  • get:聲明發送一個get請求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根據uri模板和uri變數值得到一個GET請求方式的。另外提供了其他的請求的方法,如:post、put、delete等。
  • param:新增request的引數,如上面傳送請求的時候帶上了了pcode = root的引數。假如使用需要傳送json資料格式的時將不能使用這種方式,可見後面被@ResponseBody註解引數的解決方法
  • andExpect:新增ResultMatcher驗證規則,驗證控制器執行完成結果是否正確(對返回的資料進行的判斷);
  • andDo:新增ResultHandler結果處理器,比如除錯時列印結果到控制檯(對返回的資料進行的判斷);
  • andReturn:最後返回相應的MvcResult;然後進行自定義驗證/進行下一步的非同步處理(對返回的資料進行的判斷);

注意事項:

  • 在mac上使用log4j是,假如使用了${catalina.home}需要注意,mac不會去找到tomcat所在的路徑,直接回到根路徑 “/”,而正常情況下,根路徑是沒有寫許可權的,需要使用管理員賦許可權。
  • log4j在配置完成之後,需要設定起列印日誌的級別,假如沒有設定,在Junit中,將無法列印日誌。 

3、後臺的返回資料中,最好帶上我們對資料庫的修改的結果返回的前端。

為什麼要在data中返回一個修改或者新增的物件

  • 將資料返回給前端,前端容易判斷資料是否新增或者修改成功
  • 更新或者新增完資料經常需要重新整理頁面,將資料直接給了前端,前端不用再發一個請求來獲取
  • 單元測試的時候,能對資料庫的DDL(增刪改)操作的時候,我們能對資料進行稽核,從何判斷我們的操作是否是成功的。如下面的例子:

我們傳送一個新增操作,新增一個SoftInfo物件,SoftInfo類定義如下:

public class SoftInfo {
    private String id;
    private String name;
}

新增完之後,由於我們進行了單元測試的事務回滾,我們將不能再資料庫中看我們我們的的新增操作,無法判斷操作是否成功

為了解決上面的問題,我們可以在返回的json的資料中新增一個“data”欄位,解析該json中的data欄位資料,判斷我們的新增操作是否成功的。json格式如下:

{
    "status":200,
    "data":{"id":"2","name":"測試"}
}

我們可以使用andExpect方法對返回的資料進行判斷,用“$.屬性”獲取裡面的資料,如我要獲取返回資料中的"data.name",可以寫成"$.data.name"。下面的例子是判斷返回的data.name=“測試”。

複製程式碼

@Test
    public void testCreateSeewoAccountUser() throws Exception {
        mockMvc.perform(post("/users")
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)  
        ).andExpect(status().isOk())
        .andExpect(jsonPath("$.data.name", is("測試"))))  
        .andExpect(jsonPath("$.data.createTime", notNullValue()))
        ;
    }

複製程式碼

三、遇到的問題

1、傳送一個被@ResponseBody標識的引數,一直到400錯誤。 即無法傳送一個json格式的資料到Controller層。

解決方法1:

複製程式碼

      SoftInfo softInfo = new SoftInfo();
      //設定值
     ObjectMapper mapper = new ObjectMapper();
        ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
        java.lang.String requestJson = ow.writeValueAsString(softInfo);
        String responseString = mockMvc.perform( post("/softs").contentType(MediaType.APPLICATION_JSON).content(requestJson)).andDo(print())
                .andExpect(status().isOk()).andReturn().getResponse().getContentAsString();

複製程式碼

 解決方法2:使用com.alibaba.fastjson.JSONObject將物件轉換為Json資料

 SoftInfo softInfo = new SoftInfo();
//。。。設定值
    String requestJson = JSONObject.toJSONString(folderInfo);
        String responseString = mockMvc.perform( post("/softs").contentType(MediaType.APPLICATION_JSON).content(requestJson)).andDo(print())
                .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); 

  注意上面contentType需要設定成MediaType.APPLICATION_JSON,即宣告是傳送“application/json”格式的資料。使用content方法,將轉換的json資料放到request的body中。

2、java.lang.NoClassDefFoundError: com/jayway/jsonpath/InvalidPathException

缺少了jar包:

可以新增一下的maven依賴

複製程式碼

     <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>0.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path-assert</artifactId>
            <version>0.8.1</version>
            <scope>test</scope>
        </dependency>

複製程式碼

  致謝:感謝您的閱讀!