来源:http://blog.csdn.net/victor_cindy1/article/details/52126161
最近在公司用的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对象是MailService,使用了MailService的是MailController,所以在Controller加上该Annotate;
Setup方法
MockitoAnnotations.initMocks(this): 将打上Mockito标签的对象起作用,使得Mock的类被Mock,使用了Mock对象的类自动与Mock对象关联。
mockMvc: 细心的朋友应该注意到了这个对象,这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。
Test Case
首先mock了MailService的send方法,让其返回一个成功的Result对象。
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对象来模拟Controler的被测方法,而在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>