最近在公司用的Spring Mvc REST API框架做了一个项目,并且做了基于Spring的单元测试,今天先讲一下基于Spring框架的单元测试,测试使用的是Spring自带的test组件,再结合Mockito一起编写测试案例,以下示例会包括Controller和Service,由于Repository没有自己的逻辑,所以这里就不涉及Repository的单元测试。
首先看一下RestController的代码:
package com.dhb.springmvc.controller;
import com.dhb.springmvc.entity.User;
import com.dhb.springmvc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* Created by ${denghb} on 2016/7/31.
*/
@RestController
@RequestMapping("/springmvc")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/{name}", method = RequestMethod.GET)
public String sayHello(@PathVariable String name) {
return name;
}
@RequestMapping(value = "/api/addUser", method = RequestMethod.POST)
public int addUserInfo(@RequestBody User user) {
int result = userService.addUser(user);
return result;
}
@RequestMapping(value = "/api/getUser/{id}", method = RequestMethod.GET)
public User getUserInfo(@PathVariable int id) {
return userService.findOneUser(id);
}
@RequestMapping(value = "/api/registerUser", method = RequestMethod.POST)
public Object registerUser(@RequestBody User user) {
return userService.register(user);
}
}
Service的功能代码,代码也比较简单,就是调用Repository做一些增删改查的动作。
package com.dhb.springmvc.service;
import com.dhb.springmvc.entity.User;
import com.dhb.springmvc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by ${denghb} on 2016/8/2.
*/
@Service
public class UserService {
@Autowired
UserDao userDao;
public int addUser (User user) {
return userDao.addUser(user);
}
public User findOneUser(int id) {
return userDao.findOneUser(id);
}
public String register(User user) {
User user2 = userDao.findUserByName(user.getName());
if(user2 == null) {
userDao.addUser(user);
return "成功";
} else {
return "失败";
}
}
}
下面是repository代码:
package com.dhb.springmvc.repository;
import com.dhb.springmvc.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
/**
* Created by ${denghb} on 2016/8/2.
*/
@Repository
public class UserDao {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int addUser(User user) {
String name = user.getName();
String password = user.getPassword();
RowMapper<User> mapper = new BeanPropertyRowMapper<>(User.class);
int result = jdbcTemplate.update("insert into user(name, password) values(?,?)",name, password);
return result;
}
public User findOneUser(int id) {
RowMapper<User> mapper = new BeanPropertyRowMapper<>(User.class);
User user = jdbcTemplate.queryForObject("select id, name, password from user where id = ?", mapper, id);
return user;
}
public User findUserByName(String name) {
RowMapper<User> mapper = new BeanPropertyRowMapper<>(User.class);
User user = jdbcTemplate.queryForObject("select id, name, password from user where name = ?", mapper, name);
return user;
}
}
entity类:
package com.dhb.springmvc.entity;
/**
* Created by ${denghb} on 2016/8/1.
*/
//@XmlRootElement(name = "demo")
//@XmlRootElement
public class User {
int id;
String name;
String password;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
//@XmlElement
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//@XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//@XmlElement
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
增加一个C3P0配置:
package com.dhb.springmvc.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* Created by ${denghb} on 2016/8/2.
*/
@Configuration
//@PropertySource(value = {"classpath:c3p0.properties"})
public class C3P0DataSourceBuilder {
/**
* 配置数据源
* @return
*/
@Bean(name = "dataSource")
public ComboPooledDataSource getDataSource() {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/demo");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setMaxPoolSize(75);
return dataSource;
} catch (Exception e) {
return null;
}
}
}
先看对应的RestController测试:
package com.dhb.springmvc.controller;
import com.dhb.springmvc.config.DhbWebApplicationInitializer;
import com.dhb.springmvc.entity.User;
import com.dhb.springmvc.service.UserService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
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 static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Created by ${denghb} on 2016/8/2.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {DhbWebApplicationInitializer.class})
public class UserControllerTest {
private MockMvc mockMvc;
@Mock
private UserService userService;
@InjectMocks
private UserController userController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
@Test
public void testAdd() {
int id = 1;
String name = "邓海波";
String password = "123456";
User user = new User();
user.setId(id);
user.setName(name);
user.setPassword(password);
when(userService.addUser(user)).thenReturn(1);
int restUser = userController.addUserInfo(user);
assertEquals(1, restUser);
verify(userService).addUser(user);
}
@Test
public void testGetUserInfo() throws Exception {
int userId = 1;
String userName = "邓海波";
String userPassword = "123456";
User user = new User();
user.setId(userId);
user.setName(userName);
user.setPassword(userPassword);
when(userService.findOneUser(userId)).thenReturn(user);
mockMvc.perform(get("/springmvc/api/getUser/{id}", userId))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("id").value(userId))
.andExpect(jsonPath("name").value(userName))
.andExpect(jsonPath("password").value(userPassword));
verify(userService).findOneUser(userId);
}
}
首先是Spring的几个Annotate
RunWith(SpringJUnit4ClassRunner.class): 表示使用Spring Test组件进行单元测试;
WebAppConfiguration: 使用这个Annotate会在跑单元测试的时候真实的启一个web服务,然后开始调用Controller的Rest API,待单元测试跑完之后再将web服务停掉;
ContextConfiguration: 指定Bean的配置文件信息,可以有多种方式,这个例子使用的是文件路径形式,如果有多个配置文件,可以将括号中的信息配置为一个字符串数组来表示;
然后是Mockito的Annotate
Mock: 如果该对象需要mock,则加上此Annotate;
InjectMocks: 使mock对象的使用类可以注入mock对象,在上面这个例子中,mock对象是UserService,使用了UserService的是UserController,所以在Controller加上该Annotate;
Setup方法
MockitoAnnotations.initMocks(this): 将打上Mockito标签的对象起作用,使得Mock的类被Mock,使用了Mock对象的类自动与Mock对象关联。
mockMvc: 细心的朋友应该注意到了这个对象,这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。
Test Case
mockMvc.perform: 发起一个http请求。
post(url): 表示一个post请求,url对应的是Controller中被测方法的Rest url。
param(key, value): 表示一个request parameter,方法参数是key和value。
andDo(print()): 表示打印出request和response的详细信息,便于调试。
andExpect(status().isOk()): 表示期望返回的Response Status是200。
andExpect(content().string(is(expectstring)): 表示期望返回的Response Body内容是期望的字符串。
使用print打印处理的信息类似下面显示的内容:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /springmvc/api/getUser/1
Parameters = {}
Headers = {}
Handler:
Type = com.dhb.springmvc.controller.UserController
Method = public com.dhb.springmvc.entity.User com.dhb.springmvc.controller.UserController.getUserInfo(int)
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 = {"id":1,"name":"邓海波","password":"123456"}
Forwarded URL = null
Redirected URL = null
Cookies = []
再来看一下service对应的测试代码:
package com.dhb.springmvc.service;
import com.dhb.springmvc.config.DhbWebApplicationInitializer;
import com.dhb.springmvc.entity.User;
import com.dhb.springmvc.repository.UserDao;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Created by ${denghb} on 2016/8/3.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DhbWebApplicationInitializer.class})
public class UserServiceTest {
@Mock
private UserDao userDao;
@InjectMocks
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testRegister() {
when(userDao.findUserByName(anyString())).thenReturn(null);
when(userDao.addUser(any(User.class))).thenReturn(0);
assertThat(userService.register(new User("邓海波", "567890")), is("成功"));
verify(userDao).findUserByName(anyString());
verify(userDao).addUser(any(User.class));
}
}
Service的单元测试就比较简单了,大部分内容都在RestController里面讲过,不同的地方就是RestController是使用mockMvc对象来模拟Controller的被测方法,而在Service的单元测试中则是直接调用Service的方法。
这是pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dhb.demo</groupId>
<artifactId>spring4MVCHelloWorldNoXMLDemo</artifactId>
<packaging>war</packaging>
<version>0.1.0-SNAPSHOT</version>
<name>spring4MVCHelloWorldNoXMLDemo Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<jetty.context>/</jetty.context>
<jetty.http.port>9089</jetty.http.port>
<jetty.https.port>9444</jetty.https.port>
<jetty.stopPort>10081</jetty.stopPort>
<spring.version>4.1.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<!-- jsonPath -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>2.2.0</version>
</dependency>
<!--c3po-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<!--maven jetty 插件配置-->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.17.v20160517</version>
<configuration>
<webApp>
<contextPath>${jetty.context}</contextPath>
</webApp>
<httpConnector>
<port>${jetty.http.port}</port>
</httpConnector>
<stopKey>jetty</stopKey>
<stopPort>${jetty.stopPort}</stopPort>
<!--<scanIntervalSeconds>2</scanIntervalSeconds>-->
</configuration>
</plugin>
</plugins>
<finalName>Spring4MVCHelloWorldNoXMLDemo</finalName>
</build>
</project>
这里介绍省略了一些spring web环境的配置,可以去我前面几篇博客去看哈~