Spring Boot+JUnit5+Mockito单元测试

Spring Boot+JUnit5+Mockito单元测试

导语:

最近领导要求项目添加单元测试,指定用JUnit5Mockito,之前没玩过这两个东西,这几天在网上查了很多资料,略懂皮毛,这篇文章打算把这些东西整理出来。

一,单元测试和JUnit

单元测试

要玩这个东西,首先得知道什么是单元测试,这个很重要,我之前一直纠结写单元测试的意义在哪。
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元就是方法,因此,对Java程序进行单元测试就是针对单个Java方法进行测试。

先了解一下什么是测试驱动开发
所谓测试驱动开发,是指先编写接口,紧接着编写测试。编写完测试后,我们才开始真正编写实现代码。在编写实现过程中,我们一边写一边测,什么时候测试全部通过了,就表示编写的实现完成了。而当接口需要修改时,修改完只要跑一遍单元测试,如果过了,说明修改没有问题。
然而这是一种理想的情况,现实中大部分情况都是我们已经写好了实现的代码,需要对已有的代码进行测试。

在没有用JUnit之前我们通常会使用main()来运行测试代码,接口实现写完了,用main()跑一遍,但是使用main()有很多缺点,一是不能把测试代码分离,二是main()只能有一个,三是main()是静态的会影响很多测试,谁用谁知道。

所以我们需要了解一下JUnit框架。

JUnit框架

JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit目前最新版本是JUnit5
关于JUnit的使用这里就不写了,网上教程一大把,贴上个比较完整的教程JUnit教程
Maven依赖

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.5.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>

二,Mockito框架

既然是对程序中的某个方法进行测试,难免会遇到方法中有外部依赖的时候,这时候就可以用Mockito来进行模拟外部依赖,比如一个controller中有service调用,这时就可以用Mockito把这个Service对象Mock掉,真正的实现"单元"测试。
关于Mockito使用,这里也贴上教程Mockito教程

三,Controller层测试示例

了解完这两个东西就可以使用他们来测试代码了,首先看下Controller层,在Controller层中我们的每个方法都是一个HTTP请求的入口,所以我们要测试Controller代码需要模拟HTTP请求,使用MockMvc就可以做到。

MockMvc的使用方式:

  1. MockMvcBuilder构造MockMvc的构造器
  2. MockMvcRequestBuilders创建请求request
  3. mockMvc调用perform,执行一个request请求,调用controller的业务处理逻辑,返回ResultActions
  4. 可以通过ResultActions, MockMvcResultMatchers对结果进行验证

示例一(GET请求不带参数):

@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
    private MockMvc mvc;
     
    @BeforeEach
    public void setUp() {
        // (1)构建mvc环境
        mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }
    
    @Test
    public void test() throws Exception {
        // (2)构建请求
        MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/account/info")
                .contentType("text/html")
                .accept(MediaType.APPLICATION_JSON);
  
        // (3)发送请求,获取请求结果
        ResultActions perform = mvc.perform(request);
  
        // (4)请求结果校验
        perform.andExpect(MockMvcResultMatchers.status().isOk());
  
        MvcResult mvcResult = perform.andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
  
        // (5)校验返回信息
         log.info(response.getContentAsString());
    }
}  

示例二(POST请求带token和参数对象):

如果是POST请求带参数对象,可以使用MockMvcRequestBuilders.post方法模拟POST请求

@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
    private MockMvc mvc;
     
    @BeforeEach
    public void setUp() {
        // (1)构建mvc环境
        mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }
    
    @Test
    public void test() throws Exception {
        // (2)构建请求
        MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post("/account/info")
                .header("Authorization",token)
                .contentType(MediaType.APPLICATION_JSON)
                .content(JSON.toJSONString(params));
  
        // (3)发送请求,获取请求结果
        ResultActions perform = mvc.perform(request);
  
        // (4)请求结果校验
        perform.andExpect(MockMvcResultMatchers.status().isOk());
  
        MvcResult mvcResult = perform.andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
  
        // (5)校验返回信息
         log.info(response.getContentAsString());
    }
} 

示例三(使用Mockito模拟掉外部依赖):

Controller中一般都会依赖其他Service服务,可以使用Mockito模拟掉,只专注于Controller层的测试,假设AccountController依赖于AccountService中的service方法,参考以下示例:

@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
    private MockMvc mvc;

    @Mock 
    private AccountService service;

    @InjectMocks
    private AccountController controller;
     
    @BeforeEach
    public void setUp() {
         MockitoAnnotations.initMocks(this);//这句话执行以后,service自动注入到controller中。
        
        // (1)构建mvc环境
        mvc = MockMvcBuilders.standaloneSetup(controller).build();
    }
    
    @Test
    public void test() throws Exception {

        // (2)模拟外部依赖返回结果
        when(service.service(Mockito.any(Param.class))).thenReturn("success");
        
        // (3)构建请求
        MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post("/account/info")
                .header("Authorization",token)
                .contentType(MediaType.APPLICATION_JSON)
                .content(JSON.toJSONString(params));
  
        // (4)发送请求,获取请求结果
        ResultActions perform = mvc.perform(request);
  
        // (5)请求结果校验
        perform.andExpect(MockMvcResultMatchers.status().isOk());
  
        MvcResult mvcResult = perform.andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
  
        // (6)校验返回信息
         log.info(response.getContentAsString());
    }
}   

注意:
有些依赖没法用@InjectMocks来自动注入,可以通过引入ReflectionTestUtils,解决依赖注入的问题。

ReflectionTestUtils.setField(controller,"service",service);

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 一 JUnit介绍 JUnit是一个由Java语言编写的开源的回归测试框架,由Erich Gamma和Kent B...
    十丈_红尘阅读 1,741评论 0 4
  • 第一个markdown笔记 markdown语法 1.标题:使用#来区分标题级别,使用 # 号可表示 1-6 级标...
    羌笛何须怨杨柳_阅读 241评论 0 0
  • 前端和后端交互的时候,再三确定前端传送的数据在后面接受的对象里都有,但是报了这个错: Access to XMLH...
    TowuaErio阅读 3,357评论 1 1
  • 有这样一种常见的需求:有一个搜索框,需要根据用户的输入进行实时的查询。也就是说用户每输入一个字符就要发送一次请求。...
    张艳华_zzz阅读 255评论 0 0
  • [太长了,必须单挑出来。准备好小板凳和凉白开吧。] 普通人怎么升级自己的认知呢? 我觉得由浅到深,傅盛谈了三个方面...
    alucardzhou阅读 1,689评论 0 6