IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot Test junit4 -> 正文阅读

[Java知识库]SpringBoot Test junit4

测试好处多多。但在 spring boot 里写测试,别说得到好处,就连把测试框架搭对都不是个简单的事。

毋庸置疑, 相对于 Golang, python 的网络测试框架, java 里 spring 里的测试框架真是复杂的可以. 约定优于配置, 这约定漫天飞舞藏在文档的各个角落. 版本还不统一.

一般我们写后端逻辑分 3 层, Controller -> Service -> Repository,简单来说,

对于单元测试,我们只针对某一个功能点写测试

而对于集成测试,我们会集成多个功能。

spring boot 为这两种测试,都提供了具体的约定方法。

spring boot 启动慢,我们测试时应该尽可能的只启动我们需要的类。 怎么做到呢?

spring boot 分层测试

我们看上面 spring 简化的请求图。 如果是单元测试。我们应该只对 3 测(依赖 4),只对 4 测(依赖 5)。 而集成测试,我们应该是可以测 1 到 5. 当然,这个界定根据你的需求来。

在测试前,我们先把 pom 的包统一下.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/>
</parent>
?
<dependencies>
   <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
?
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
?
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

完整源码地址:?https://github.com/zk4/spring_test_demo

测试场景

我们要将测试场景列个矩阵

不加载 spring 框架 的单元测试

这是最灵活也是最快的一种方案

拿 Controller 举例

Controller 其实就是一个类. 理论上 new 出来测就行了

但问题是: Controller 里会被 Spring 注入一堆东西.

简单的解决方法是:

不要在成员上加 @Autowired, 而是在构造时自动注入. 这也是 Spring 官方的推荐做法.

@Controller
class  UserController{
  //不要这样 
  @Autowired
  UserService userService;  
}
?

@Controller
class  UserController{
  UserService userService;
  //建议这样
  @Autowired
  UserController(UserService userService){
    this.userService = userService;
  }
}

那 UserService 直接 new? UserService 也是有 Spring 注入的.

我们知道 java 里可以给类做代理.

那么,对构造函数是类的, 我们只要代理这个类,然后模拟类函数的返回值就行了.

这个过程. spring test 里的 Mokito 做了封装.

在 Test 类里, 操作如下:

//测试类的头, 使 Mock 生效
@RunWith(MockitoJUnitRunner.class)
public class UserControllerTest{
  // 指定要 Mock 的类
  @Mock
  UserService userService;  
?  
  @Test
  public testhello(){
      // Mock 的类里的函数怎么返回
      given(userService.getUser(1))
          .willReturn(new User().setName("bob").setId(1));
      ....
  }
}

怎么模拟 http 请求访问呢?

Mockito 帮你做, 但不是真正的网络请求! 是模拟的. 就是不会经过网卡. 而是直接将模拟网络请求塞给 Controller.

见下面 @Test 方法.

package com.zk.controller;
?
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zk.entity.User;
import com.zk.exception.UserNotFound;
import com.zk.service.UserService;
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
?
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
//由 Junit 4 启动 Mockito
@RunWith(MockitoJUnitRunner.class)
@AutoConfigureRestDocs
public class MyControllerTest1 {
?
?
?
  private MockMvc mvc;
?
  @Mock
  // 要 Mock 的类
  UserService  userService;
?
  @InjectMocks
  // Mock 要注入的类
  UserController userController;
?
?
  @Before
  public void setUp()   {
    mvc = MockMvcBuilders.standaloneSetup(userController)
        //指定 Exception 处理器
        .setControllerAdvice(new UserExceptionAdvice())
        //.addFilters(new UserFilter())  //你也可以指定 filter , interceptor 之类的, 看 StandaloneMockMvcBuilder 源码
        .build();
  }
?
  @Test
  public void getUserTest() throws Exception {
    // given
    User bob = new User().setName("bob").setId(1);
    given(userService.getUser(1))
        .willReturn(bob);
?
    //  when
    MockHttpServletResponse response = mvc.perform(
        get("/user/1")
            .accept(MediaType.APPLICATION_JSON)
?
    )
        .andReturn()
        .getResponse();
?
    // then
    ObjectMapper objectMapper = new ObjectMapper();
    Assert.assertEquals(response.getStatus(), HttpStatus.OK.value());
    Assert.assertEquals(response.getContentAsString(), objectMapper.writeValueAsString(bob));
  }
?
?
  @Test
  public void getUserNotFound() throws Exception {
     //given
    given(userService.getUser(999))
        .willThrow(new UserNotFound());
?
    // when
    MockHttpServletResponse response = mvc.perform(
        get("/user/999")
            .accept(MediaType.APPLICATION_JSON))
        .andReturn().getResponse();
?
    // then
    Assertions.assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
    Assertions.assertThat(response.getContentAsString()).isEmpty();
  }
}
?

加载 spring 框架, 集成测试

这个相对来说最简单的. 也是最符合直觉的,

但就是, 太慢. 要启整个 Spring.

package com.zk.controller;
?
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zk.entity.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
?
?
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyControllerTest2 {
?
  // 要注意, 不要用 RestTemplate,
  // 因为 TestRestTemplate 在测试环境里多做了很多事,
  // 比如: 帮你自己把当前 host:port 加上了. (尤其是咱们还指定了随机端口)
  //      能自动加账号密码,
  //      ErrorHandler 被设成了 NoOpResponseErrorHandler.
  //      最重要的, 能在测试类里一键注入啊...  
  @Autowired
  TestRestTemplate restTemplate;
?
  @Test
  public void getUser() throws Exception {
    // given
    User user = new User().setName("bob").setId(1);
?
    // when
    ResponseEntity<User> response = restTemplate.getForEntity("/user/1", User.class);
?
?
    // then
    ObjectMapper objectMapper=new ObjectMapper();
?
    Assert.assertEquals(response.getStatusCode(),HttpStatus.OK);
    Assert.assertEquals(
        objectMapper.writeValueAsString(response.getBody()),
        objectMapper.writeValueAsString(user)
    );
  }
}
?

怎么 Mock?

假如我们要替换 UserService 的返回. 但 spring 的 interceptor, AdviceController 之类的都要正常加载咋整.

你可以按上一节的方式,也有更简单的方法.

在 @SpringBootTest 的加成下! 你可以直接使用 @AutoConfigureMockMvc

package com.zk.controller;
?
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zk.entity.User;
import com.zk.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
?
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
?
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 允许 Mock
@AutoConfigureMockMvc  
public class SpringRunner_mock {
?
?
  @Autowired
  MockMvc mvc;
?
  @MockBean
  UserService userService;
?
?
  @Test
  public void getUser() throws Exception {
    // given
    User bob = new User().setName("bob").setId(1);
    given(userService.getUser(1))
        .willReturn(bob);
?
?
    // when
    MockHttpServletResponse response = mvc.perform(
        get("/users/1")
            .accept(MediaType.APPLICATION_JSON))
        .andReturn().getResponse();
?
    // then
    ObjectMapper objectMapper=new ObjectMapper();
    assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
    assertThat(response.getContentAsString()).isEqualTo(
        objectMapper.writeValueAsString(bob)
    );
  }
}
?

加载 spring 部分框架(仅自动加载 Controller) 单元测试

通过 @WebMvcTest 指定即可. 你也可以不写指定的 Controller.class ,那你得 Mock 所有 Controller 的依赖才行.

@WebMvcTest 这个注解干的事就多了... 你可以点开源码看一眼, 有没有熟悉的 @AutoConfigureMockMvc

package com.zk.controller;
?
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zk.entity.User;
import com.zk.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
?
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
?
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class SpringRunner_unit_controller_only {
?
  @Autowired
  private MockMvc mvc;
?
// 会自动注入到 controller
  @MockBean
  private UserService userService;
?
  @Test
  public void getUser() throws Exception {
    // given
    User bob = new User().setName("bob").setId(1);
    given(userService.getUser(1))
        .willReturn(bob);
?
    // when
    MockHttpServletResponse response = mvc.perform(
        get("/users/1")
            .accept(MediaType.APPLICATION_JSON))
        .andReturn().getResponse();
?
    // then
    ObjectMapper  objectMapper = new ObjectMapper();
    assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
    assertThat(response.getContentAsString()).isEqualTo(
        objectMapper.writeValueAsString(bob)
    );
  }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:29:56  更:2022-08-06 10:31:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:54:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码