1. 程式人生 > >Spring整合Spring MVC及Mybatis進行Junit單元測試

Spring整合Spring MVC及Mybatis進行Junit單元測試

我們可以在不啟動服務的情況下,進行單元測試,以便提交出高質量的程式碼。本文以一個小例子,說明在Spring中如何進行單元測試。

一:測試Controller

1:在pom.xml檔案中引入相關依賴

  <properties>
    <!-- 設定專案編碼編碼 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- spring版本號 -->
    <spring.version>4.3.5.RELEASE</spring.version>
    <!-- mybatis版本號 -->
    <mybatis.version>3.4.1</mybatis.version>
  </properties>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12-beta-3</version>
    </dependency>

   <!--引入檔案上傳-->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
      <exclusions>
        <exclusion>
          <artifactId>commons-io</artifactId>
          <groupId>commons-io</groupId>
        </exclusion>
      </exclusions>
    </dependency>

2:編寫被測試類--UserController

import com.qiqi.juint.test.model.vo.UserVO;
import com.qiqi.juint.test.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "getUser",method = RequestMethod.GET)
    @ResponseBody
    public List<UserVO> getUserInfo(@RequestParam Integer age){
        return userService.getUserInfo(age);
    }
}

3:測試類--UserControllerTest

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //宣告一個ApplicationContext整合測試載入WebApplicationContext,作用是模擬ServletContext
@ContextConfiguration(locations={"classpath:spring/spring-application.xml"})
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

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

    @Test
    public void testGetUserInfo() throws Exception {

        MvcResult mvResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/getUser").param("age","12"))
                .andDo(print())
                .andReturn();//最後返回相應的MvcResult

    }
}

測試結果:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /user/getUser
       Parameters = {age=[12]}
          Headers = {}

Handler:
             Type = com.qiqi.juint.test.controller.UserController
           Method = public java.util.List<com.qiqi.juint.test.model.vo.UserVO> com.qiqi.juint.test.controller.UserController.getUserInfo(java.lang.Integer)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

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

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = [{"id":2,"age":12,"name":"小花"},{"id":3,"age":12,"name":"小蘭"},{"id":7,"age":12,"name":"大師兄"}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

二:測試Service

引入的依賴和測試Controller相同。測試時,在Service類上新增相應的註解就可以進行測試,不用單獨寫測試類。

import com.qiqi.juint.test.dao.UserMapper;
import com.qiqi.juint.test.model.vo.UserVO;
import com.qiqi.juint.test.service.UserService;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.transaction.Transactional;
import java.util.List;

/**
 * Created by ZhaoQiqi on 2018/11/8.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/spring-application.xml"})
@Transactional
@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = Logger.getLogger(UserServiceImpl.class);

    @Autowired
    private UserMapper userMapper;

    public List<UserVO> getUserInfo(Integer age) {

        logger.info("呼叫方法getUserInfo(Integer age)");
        return userMapper.getUserInfo(age);
    }

    @Test
    public void test(){
        List<UserVO> list = userMapper.getUserInfo(12);
        System.out.println(list);
    }
}

測試結果:

13:20:09.492 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
13:20:09.496 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [[email protected]]
13:20:09.503 [main] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [[email protected] [wrapping: [email protected]]] will be managed by Spring
13:20:09.506 [main] DEBUG c.q.j.t.dao.UserMapper.getUserInfo - ==>  Preparing: SELECT `id` as id, `age` as age, `name` as name FROM user WHERE age = ?; 
13:20:09.525 [main] DEBUG c.q.j.t.dao.UserMapper.getUserInfo - ==> Parameters: 12(Integer)
13:20:09.541 [main] DEBUG c.q.j.t.dao.UserMapper.getUserInfo - <==      Total: 3
13:20:09.546 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [[email protected]]
[id:2  age:12  name:小花, id:3  age:12  name:小蘭, id:7  age:12  name:大師兄]
13:20:09.546 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [[email protected]]
13:20:09.546 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [[email protected]]

 

三:在測試類中添加回滾註解,避免測試資料汙染資料庫

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //宣告一個ApplicationContext整合測試載入WebApplicationContext,作用是模擬ServletContext
@ContextConfiguration(locations={"classpath:spring/spring-application.xml"})

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

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

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

    @Test
    public void testGetUserInfo() throws Exception {

        MvcResult mvResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/getUser")
                .param("age","12"))
                .andDo(print())
                .andReturn();//最後返回相應的MvcResult

    }

    @Test
    public void testInsertUser() throws Exception {
        User user = new User(15,"哈哈");
        String requestJson = JSONObject.toJSONString(user);
        System.out.println(requestJson);
        MvcResult mvResult = mockMvc.perform(
                MockMvcRequestBuilders.post("/user/insertUser")
                        .contentType(MediaType.APPLICATION_JSON).content(requestJson))
                .andDo(print())
                .andReturn();//最後返回相應的MvcResult
    }
}

這樣在測試時,對資料庫的增刪改都會回滾,資料庫資料不會改變。在非測試時,即正常呼叫時,若被測試類沒有進行回滾註解,資料庫資料自然會改變。

四、相關程式碼的解釋:

@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;然後進行自定義驗證/進行下一步的非同步處理(對返回的資料進行的判斷);