基于Spring MVC(REST API)做单元测试(mockito)

最近在公司用的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环境的配置,可以去我前面几篇博客去看哈~


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值