springboot使用单元测试实战

前言

springboot提供了 spirng-boot-starter-test 以供开发者使用单元测试,在引入 spring-boot-starter-test 依赖后:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

其中包含以下几个库:

  • Junit ——常用的单元测试库
  • Spring Test & Spring Boot Test ——对Spring应用的集成测试支持
  • AssertJ——一个断言库
  • Hamcrest—— 一个匹配对象的库
  • Mockito—— 一个Java模拟框架
  • JSONassert—— 一个针对JSON的断言库
  • JsonPath—— 用于JSON的XPath

下面我们将从Service层和Controller层的角度来简单介绍下单元测试

Service单元测试

在SpringBoot 2.0中,创建一个Service的单元测试,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
  @Autowired
  private UserService userService;
  @Test
  public void insertUser() {
    User user = new User();
    user.setUsername("li ning");
    user.setPassword("123456");
    userService.insertUser(user);
  }
}

上面的测试非常简单,主要需要注意两个注解: @RunWith 和 @SpringBootTest

  1. @RunWith : 该注解标签是Junit提供的,用来说明此测试类的运行者,这里用了 SpringRunner ,它实际上继承了 SpringJUnit4ClassRunner 类,而 SpringJUnit4ClassRunner 这个类是一个针对Junit 运行环境的自定义扩展,用来标准化在Springboot环境下Junit4.x的测试用例
  2. @SpringBootTest 为 springApplication创建上下文并支持SpringBoot特性

使用 @SpringBootTest 的 webEnvironment 属性定义运行环境:

  1. Mock(默认) : 加载WebApplicationContext 并提供模拟的web环境 Servlet环境,使用此批注时,不会启动嵌入式服务器
  2. RANDOM_PORT : 加载WebServerApplicationContext 并提供真实的web环境,嵌入式服务器, 监听端口是随机的
  3. DEFINED_PORT : 加载WebServerApplicationContext并提供真实的Web环境,嵌入式服务器启动并监听定义的端口(来自 application.properties或默认端口 8080)
  4. NONE : 使用SpringApplication加载ApplicationContext 但不提供任何Web环境

Controller的单元测试

首先创建一个Controller,代码如下:

@RestController
public class UserController {
  @Autowired
  private UserService userService;
  @PostMapping("/user")
  public String userMapping(@RequestBody User user){
    userService.insertUser(user);
    return "ok";
  }
}

然后创建Controller的单元测试,一般有两种创建方法。

第一种使用模拟环境进行测试

默认情况下,@SpringBootTest 不会启动服务器,如果需针对此模拟环境测试Web端点,可以如下配置 MockMvc:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @Test
  public void userMapping() throws Exception {
    String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
    mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST, "/user")
            .contentType("application/json").content(content))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.content().string("ok"));
  }
}

这里有一个 @AutoConfigureMockMvc 注解,该注解表示启动测试的时候自动注入 MockMvc ,而这个 MockMvc 有以下几个基本的方法:

  • perform : 执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理。
  • andExpect: 添加RequsetMatcher验证规则,验证控制器执行完成后结果是否正确
  • andDo: 添加ResultHandler结果处理器,比如调试时打印结果到控制台
  • andReturn: 最后返回相应的MvcResult,然后进行自定义验证/进行下一步的异步处理

这里有一个小技巧,一般来说对于一个controller中往往有不止一个Request请求需要测试,敲打MockMvcRequestBuilders与MockMvcResultMatchers会显得比较繁琐,有一个简便的方法就是将这两个类的方法使用 import static 静态导入,然后就可以直接使用两个类的静态方法了。然后代码就变成如下所示:

...
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @Test
  public void userMapping() throws Exception {
    String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
    mockMvc.perform(request(HttpMethod.POST, "/user")
            .contentType("application/json").content(content))
        .andExpect(status().isOk())
        .andExpect(content().string("ok"));
  }
}

另外,如果是只想关注Web层而不是启动完整的ApplicationContext,可以考虑使用 @WebMvcTest 注解,该注解不能与@SpringBootTest搭配使用,而且它只关注Web层面,至于涉及到数据层的时候,需要引入相关依赖,关于这个注解更多的介绍请参阅官方文档: https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests

使用MockMvcBuilder构建MockMvc对象

除了上面用 @AutoConfigureMockMvc 注解直接自动注入 MockMvc的方式,我们还可以利用MockMvcBuilder来构建MockMvc对象,示例代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest4 {
  @Autowired
  private WebApplicationContext web;
  private MockMvc mockMvc;

  @Before
  public void setupMockMvc() {
    mockMvc = MockMvcBuilders.webAppContextSetup(web).build();
  }
  @Test
  public void userMapping() throws Exception {
    String content = "{\"username\":\"pj_m\",\"password\":\"123456\"}";
    mockMvc.perform(request(HttpMethod.POST, "/user")
            .contentType("application/json").content(content))
        .andExpect(status().isOk())
        .andExpect(content().string("ok"));
  }
}

第二种使用真实Web环境进行测试

在@SpringBootTest注解中设置属性 webEnvironment = WebEnvironment.RANDOM_PORT ,每次运行的时候会随机选择一个可用端口。我们也可以还使用 @LoalServerPort 注解用于本地端口号。下面是测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest3 {
  @Autowired
  private TestRestTemplate testRestTemplate;
  @Test
  public void userMapping() throws Exception {
    User user = new User();
    user.setUsername("pj_pj");
    user.setPassword("123456");
    ResponseEntity<String> responseEntity = testRestTemplate.postForEntity("/user", user, String.class);
    System.out.println("Result: "+responseEntity.getBody());
    System.out.println("状态码: "+responseEntity.getStatusCodeValue());
  }
}

上面的代码中有一个关键的类—— TestRestTemplate , TestRestTemplate是Spring的RestTemplate的一种替代品,可用于集成测试,更RestTemplate的使用功能方法类似,一般用于真实web环境测试中,关于该类更加详细的用法参考官方文档: https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-test-scope-dependencies

单元测试回滚

单元测试的时候,如果不想造成垃圾数据,可以开启事务功能,在方法或类头部添加 @Transactional 注解即可,在官方文档中对此也有说明:

If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case

解读一下,在单元测试中使用 @Transactional 注解,默认情况下在测试方法的末尾会回滚事务。然而有一些特殊情况需要注意,当我们使用 RANDOM_PORT 或 DEFINED_PORT 这种安排隐式提供了一个真正的Servlet环境,所以HTTP客户端和服务器将在不同的线程中运行,从而分离事务,这种情况下,在服务器上启动的任何事务都不会回滚。

当然如果你想关闭回滚,只要加上 @Rollback(false) 注解即可, @Rollback 表示事务执行完回滚,支持传入一个value,默认true即回滚,false不回滚。

还有一种情况需要注意,就是如果你使用的数据库是MySQL,有时候会发现加了注解 @Transactionl 也不会回滚,那么你就要查看一下你的默认引擎是不是InnoDB,如果不是就要改成 InnoDB。

MyISAM 与 InnoDB是mysql目前比较常用的两个数据库引擎,MyISAM与InnoDB的主要的不同点在于性能和事务控制上,这里简单介绍下两者的区别与转换方法:

  • MyISAM : MyISAM是MySQL5.5之前版本默认的数据库存储引擎,MyISAM提供高速存储和检索,以及全文搜索能力,适合数据仓库等查询频繁的应用,但 不支持事务和外键,不能在表损坏后恢复数据
  • InnoDB : InnoDB是MySQL5.5版本的默认数据库存储引擎,InnoDB具有提交,回滚和崩溃恢复能力的事务安全, 支持事务和外键 ,比起MyISAM,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

如果你的数据表是MyISAM引擎,由于它不支持事务,在单元测试中添加事务注解,测试方法也是不会回滚的。

修改默认引擎

查看MySQL当前默认的存储引擎

mysql> show variables like '%storage_engine%';

看具体的表user表用了什么引擎(engine后面的就表示当前表的存储引擎)

mysql> show create table user;

将user表修为InnoDB存储引擎

mysql> ALTER TABLE user ENGINE=INNODB;

注意

这里还有一点需要注意的地方, 当我们使用Spring Data JPA时,如果没有指定MySQL建表时的存储引擎,默认情况下会使用MySQL的MyISAM ,这也是一个坑点,这种情况下,你在单元测试使用 @Transactional 注解,回滚不会起作用。

解决方法是将 hibernate.dialect 属性配置成 hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect ,指定MySQL建表的时候使用 InnoDB引擎,示例配置文件如下:

spring:
 jpa:
  # 数据库类型
  database: mysql
  # 输出日志
  show-sql: true
  properties:
   hibernate:
    # JPA配置
    hbm2ddl.auto: update
    # mysql存储类型配置
    dialect: org.hibernate.dialect.MySQL5InnoDBDialect

小结

上面简单总结了springboot下如何使用单元测试,关于单元测试更加详细的介绍请参阅官方文档: https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing

参考资料 & 鸣谢

springboot官方文档 https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解SpringBoot restful api的单元测试

    现在我们来利用Spring Boot来构建一个RestFul API,具体如下: 1.添加Springboot测试注解 @RunWith(SpringRunner.class) @SpringBootTest public class UserControllerTest { } 2.伪造mvc环境 // 注入Spring 工厂 @Autowired private WebApplicationContext wac; //伪造mvc环境 private MockMvc mockMvc; @Be

  • 基于Springboot+Junit+Mockito做单元测试的示例

    前言 这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取撮合交易的一个类来做单元测试. 单元测试前先理解需求 要写出好的单测,必须先理解了需求,只有知道做什么才能知道怎么测.但本文主要讲mockito的用法,无需关注具体需求.所以本节略去具体的需求描述. 隔离外部依赖 Case1. 被测类中被@Autowired 或 @Resource 注解标注的依赖对象,如何控制其返回值 以被测方法 MatchingServiceImpl.java的matching(Ma

  • 详解Spring Boot实战之单元测试

    本文介绍使用Spring测试框架提供的MockMvc对象,对Restful API进行单元测试 Spring测试框架提供MockMvc对象,可以在不需要客户端-服务端请求的情况下进行MVC测试,完全在服务端这边就可以执行Controller的请求,跟启动了测试服务器一样. 测试开始之前需要建立测试环境,setup方法被@Before修饰.通过MockMvcBuilders工具,使用WebApplicationContext对象作为参数,创建一个MockMvc对象. MockMvc对象提供一组工具

  • springboot整合H2内存数据库实现单元测试与数据库无关性

    一.新建spring boot工程 新建工程的时候,需要加入JPA,H2依赖 二.工程结构 pom文件依赖如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:s

  • 详解SpringBoot之添加单元测试

    本文介绍了详解SpringBoot之添加单元测试,分享给大家,希望此文章对各位有所帮助 在SpringBoot里添加单元测试是非常简单的一件事,我们只需要添加SpringBoot单元测试的依赖jar,然后再添加两个注解就可搞定了. 首先我们来添加单元测试所需要的jar <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test<

  • 详解Spring Boot Junit单元测试

    Junit这种老技术,现在又拿出来说,不为别的,某种程度上来说,更是为了要说明它在项目中的重要性. 凭本人的感觉和经验来说,在项目中完全按标准都写Junit用例覆盖大部分业务代码的,应该不会超过一半. 刚好前段时间写了一些关于SpringBoot的帖子,正好现在把Junit再拿出来从几个方面再说一下,也算是给一些新手参考了. 那么先简单说一下为什么要写测试用例 1. 可以避免测试点的遗漏,为了更好的进行测试,可以提高测试效率 2. 可以自动测试,可以在项目打包前进行测试校验 3. 可以及时发现因

  • springboot使用单元测试实战

    前言 springboot提供了 spirng-boot-starter-test 以供开发者使用单元测试,在引入 spring-boot-starter-test 依赖后: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope>

  • Springboot Mybatis-Plus数据库单元测试实战(三种方式)

      单元测试长久以来是热门话题,本文不会讨论需不需要写单测,可以看看参考资料1,我个人认为写好单测应该是每个优秀开发者必备的技能,关于写单测的好处在这里我就不展开讨论了,快速进入本文着重讨论的话题,如何写好数据库单测.   为什么要写数据库单测? 相信大家是不是有这样类似的经历,在写完复杂的sql语句后,自信满满的提测,发现很大一部分Bug都是因为sql语句出现问题了,要么少写逗号,要么漏了字段,悔不当初哇,为啥写完不多测测呢!   没关系!这就教你如何写数据库单测,让你轻松告别数据库相关bug

  • springboot集成junit编写单元测试实战

    目录 一:查看jar包版本号是否为junit4: 二:实战应用: 三:扩展 在做单元测试时,代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到80%或 90%.于是乎,测试人员费尽心思设计案例覆盖代码.用代码覆盖率来衡量,有利也有弊. 首先,让我们先来了解一下所谓的“代码覆盖率”.我找来了所谓的定义:代码覆盖率 = 代码的覆盖程度,一种度量方式. 一:查看jar包版本号是否为junit4: junit自身注解: @BeforeClass

  • SpringBoot 单元测试实战(Mockito,MockBean)

    目录 一个测试方法主要包括三部分 Junit 基本注解介绍 测试方法执行顺序 测试方法命名约定 基于 Spring 的单元测试编写 Mockito 常用的 Mockito 方法 示例 @MockBean 一个测试方法主要包括三部分 1)setup 2)执行操作 3)验证结果 public class CalculatorTest { Calculator mCalculator; @Before // setup public void setup() { mCalculator = new C

  • 浅谈spring-boot的单元测试中,@Before不被执行的原因

    我们先来看下笔者的单元测试的依赖版本: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from reposi

  • SpringBoot+TestNG单元测试的实现

    目录 背景 接口测试用例,针对入参进行设计: 言归正传! 背景 由于开发任务进度紧张,接口及基础数据提供不全,即使设计全面的接口测试用例也无法全面有效的进行覆盖测试:且又因为单接口测试用例设计的方向是入参和出参,从入参着手就是参数必填校验.参数类型及参数边界值,再有入参的组合入参,例如一个接口5个参数,3个必填,2个非必填,数据类型有string.int等,还有字符长度限制条件,那么这样的单接口测试用例数设计起来那就有,嗯......数不过来,如果入参个数及参数类型变得多起来,那么这个数量就不可

  • springboot整合过滤器实战步骤

    目录 一.普通的接口访问 二.增加一个过滤器 1.自定义过滤器 2.注册到容器 3.演示一下效果: 三.增加两个过滤器 下面先建立一个MVC的基本请求接口,如下: 一.普通的接口访问 如上,先新增一个testController. 先用postman测试一下通不通. 结果是通的,准备工作完成. 二.增加一个过滤器 下面增加一个过滤器来实现一个接口拦截并处理token校验的模拟. 简单处理,有如下两个步骤. 1.自定义过滤器 package com.example.demo_filter_inte

  • SpringBoot整合RabbitMQ实战教程附死信交换机

    目录 前言 环境 配置 配置文件 业务消费者 死信消费者 测试 前言 使用springboot,实现以下功能,有两个队列1.2,往里面发送消息,如果处理失败发生异常,可以重试3次,重试3次均失败,那么就将消息发送到死信队列进行统一处理,例如记录数据库.报警等完整demo项目代码https://gitee.com/daenmax/rabbit-mq-demo 环境 Windows10,IDEA,otp_win64_25.0,rabbitmq-server-3.10.41.双击C:\Program

  • SpringBoot与单元测试JUnit的结合操作

    目录 Juint版本说明 Junit5常见注解及其用法 在普通Maven项目中使用Junit 在Spring项目中使用Junit 在SpringBoot项目中使用Junit 有些人认为,写单元测试就是在浪费时间 ,写完代码,依然还是能够进行测试的.但是,还是建议写单元测试的,可以让你的条理更加清晰,而且当某个功能出现问题时,可能通过单元测试很容易的定位和解决问题. 本文主要总结下在Spring及SpringBoot项目中,使用单元测试时的方法.将JUnit4和JUnit5对比着来写,因为我发现我

  • 基于SpringBoot Mock单元测试详解

    目录 1.Mock的概念: 3. 常用的 Mockito 方法 Junit中的基本注解: @Test:使用该注解标注的public void方法会表示为一个测试方法: @BeforeClass:表示在类中的任意public static void方法执行之前执行: @AfterClass:表示在类中的任意public static void方法之后执行: @Before:表示在任意使用@Test注解标注的public void方法执行之前执行: @After:表示在任意使用@Test注解标注的p

随机推荐