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