当前位置: 代码网 > it编程>编程语言>Java > 超详细 springboot 整合 Mock 进行单元测试!本文带你搞清楚!

超详细 springboot 整合 Mock 进行单元测试!本文带你搞清楚!

2024年08月02日 Java 我要评论
Mockito是Java单元测试开发框架。在写测试单元时它可以MockMock的中文释义是模拟,所以Mockito从名字上可以看出是要模拟一种场景它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等,避免为了测试一个方法,却要自行构建整个 bean 的依赖链。Mock 测试主要是用来进行开发中一些未完成的接口或者网络断开数据库连接错误等方法调用。如下代码所示,list 集合需要从数据库查询出来。

一、什么是mock

1、mock定义

mockito是java单元测试开发框架。在写测试单元时它可以mockmock的中文释义是模拟,所以mockito从名字上可以看出是要模拟一种场景)。

它可以模拟任何 spring 管理的 bean、模拟方法的返回值、模拟抛出异常等,避免为了测试一个方法,却要自行构建整个 bean 的依赖链。

mock 测试主要是用来进行开发中一些 未完成的接口 或者 网络断开数据库连接错误 等方法调用。

举个例子:

如下代码所示,list 集合需要从数据库查询出来。但是如果暂时数据库不能用,有需要测试,这个时候就可以进行 mock 模拟出符合条件的 list 集合进行本地测试,无需连接数据库。

在这里插入图片描述

2、为什么使用

在对代码进行单元测试过程中,经常会有以下的情况发生:

class a 依赖 class b  
class b 依赖 class c和class d  
class c 依赖 ...  
class d 依赖 ...  

1.被测对象依赖的对象构造复杂
我们想对class a进行单元测试,需要构造大量的class b、c、d等依赖造步骤多、耗时较长的对象,
对于他们的构造我们可以利用mock去构造过程复杂的对象用于class a的测试,
因为我们只是想测试class a的行为是否符合预期,我们并不需要测试依赖对象。

2.被测单元依赖的模块尚未开发完成,而被测对象需要依赖模块的返回值进行测试:
----- 比如service层的代码中,包含对dao层的调用,但dao层代码尚未实现
----- 比如web的前端依赖后端接口获取数据进行联调测试,但后端接口并未开发完成
----- 比如数据库还不能正常使用但是需要测试功能逻辑是否可行。

3、常用的mock技术

  • powermock
  • easymock
  • mockito
  • jmock

目前在 java 中主流的 mock 测试工具有 mockito、jmock、easymock 等等,而 springboot 目前内建的是 mockito 框架。

4、mokito中文文档

mokito中文官网

5、集成测试和单元测试区别

(1)集成测试
测试过程中,会启动整个spring容器,调用db 或者 依赖的外部接口等。只不过访问的环境是测试环境。这个过程最大程度还原生产环境过程,但是耗时长。

(2)单元测试
不启动整个应用,只对单个接口/类进行测试。不调用db 、外部接口,依赖的服务都mock掉,只测试代码逻辑。这个过程,测试用例耗时短。

二、api

1、mockito的api

  • mock:构建一个我们需要的对象;可以mock具体的对象,也可以mock接口
  • spy:构建监控对象
  • verify:验证某种行为
  • when:当执行什么操作的时候,一般配合thenxxx 一起使用。表示执行了一个操作之后产生什么效果
  • doreturn:返回什么结果
  • dothrow:抛出一个指定异常
  • doanswer:做一个什么相应,需要我们自定义answer
  • times:某个操作执行了多少次
  • atleastonce:某个操作至少执行一次
  • atleast:某个操作至少执行指定次数
  • atmost:某个操作至多执行指定次数
  • atmostonce:某个操作至多执行一次
  • donothing:不做任何处理
  • doreturn:返回一个结果
  • dothrow:抛出一个指定异常
  • doanswer:指定一个操作,传入answer
  • docallrealmethod:返回真实业务执行的结果,只能用于监控对象

2、argumentmatchers参数匹配

  • anyint:任何int类型的参数,类似的还有anylong/anybyte等等。
  • eq:等于某个值的时候,如果是对象类型的,则看tostring方法
  • isa:匹配某种类型
  • matches:使用正则表达式进行匹配

3、ongoingstubbing返回操作

  • thenreturn:指定一个返回的值
  • thenthrow:抛出一个指定异常
  • then:指定一个操作,需要传入自定义answer
  • thencallrealmethod:返回真实业务执行的结果,只能用于监控对象

三、mockito的使用

1、添加maven依赖

  • java 环境依赖
<!--		https://mvnrepository.com/search?q=mockito-->
<dependency>
  <groupid>org.mockito</groupid>
  <artifactid>mockito-core</artifactid>
  <version>4.4.0</version>
</dependency>
  • springboot 环境依赖

注意:springboot 默认的 mock 框架是 mockito,和 junit 一样,只需要依赖 spring-boot-starter-test 就可以了

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-test</artifactid>
</dependency>

2、@injectmocks、@mock使用

  • @mock: 用于代替mockito.mock创建mock对象,创建一个mock实例,需要基于junit5环境。
  • @injectmocks: 创建一个实例,其余用@mock(或@spy)注解创建的mock将被注入到用该实例中

直白的理解就是:

如图,实体类tuserserviceimpl通过注解 @autowired注入了三个实体:tusermapper、jdbctemplate 和 namedparameterjdbctemplate。
在这里插入图片描述
如果想要测试tuserserviceimpl,那么test中,tusermapper、jdbctemplate 和 namedparameterjdbctemplate就要用@mock注解创建mock实例,而要被测试tuserserviceimpl就要用@injectmocks注解创建实例,这个时候被@mock注解创建的tusermapper、jdbctemplate 和 namedparameterjdbctemplate就被注入到通过 @injectmocks注解创建的tuserserviceimpl实例中。如下图所示:
在这里插入图片描述

最后的大白话解释 !!!

你要测试哪个类(如tuserserviceimpl),那么就用 @injectmocks注解;
被测试的类中通过 @autowired注解注入了几个,那么测试类里面就用@mock注解创建几个实例!

使用mockito的注解,需要让注解生效,让注解生效的方法有两个:

  1. 给被测类添加 @runwith(mockitojunitrunner.class) 或者 @runwith(springjunit4classrunner.class) 注解
@runwith(mockitojunitrunner.class)
public class mockitoannotationtest {
    ...
}
  1. 在初始化方法中使用mockitoannotations.openmocks(this)
@before
public void init() {
    mockitoannotations.openmocks(this);
}

注意:新版spring-boot-starter-test不再集成junit,而是junit-jupiter,找不到@runwith注解:

  • spring-boot-starter-test 2.5.5 版本只需要在类上加上@springboottest即可,不需要再加@runwith()注解了。
  • spring-boot-starter-test 2.4.x 版本的也没有@runwith()注解,至于从哪个版本开始没有@runwith()注解的,请自行查阅相关文档。
  • 一些较低版本也没有 openmocks 方法,而是 initmocks

3、springboottest 注解和 runwith 注解在测试类的作用

  • @springboottest

这个注解相当于启动类的作用,加了这个注解后,当使用加了@test注解的方法时,会加载spring上下文,跟springbootapplication这个启动类一样,把bean加载进ioc容器。

其中参数classes 需指明启动类.class,如果不指明,需要保证启动类所在的包和加了springboottest注解的类 在同一个包或者是启动类的子包下,否则注入到( @autowired / @resource)会报空指针异常。如下:

@springboottest(classes = myspringbootapplication.class)
  • @runwith

@runwith(springrunner.class),作用是与spring环境整合,因为在测试类中我们可以需要用@autowired自动装配ioc容器中的bean,所以需要与spring环境进行整合,才能实现自动装配,否则会装配失败,导致bean为null。

有时候会发现,有的测试类不添加@runwith也能注入成功,这是因为,如果导入@test注解的包是org.junit.jupiter.api.test,则不需要添加@runwith注解,如果导入的是org.junit.test,则需要添加,这点需要注意。

四、mock 测试代码案例

1、添加依赖

 		<dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
        </dependency>

注意:本案例用的 springboot 版本是 2.6 版本

在这里插入图片描述

2、编写业务代码

@service
public class positionservice {

    public string getstr(string str){
        return str;
    }
 
 	
 	public void getvoid(){
        system.out.println("没有返回值");
    }
    
}

3、mock 测试

(1)常规测试

先不使用 mockito ,而是真的去调用一个正常的 spring bean ,测试类写法如下。其实就是很普通的注入 positionservice bean,然后去调用他的方法。

测试代码:

注意:

可以看到测试类中引用的包是 org.junit.jupiter.api.testorg.junit.jupiter.api.beforeeach,是 jupiter.api 包下面的,此时测试类只用了 @springboottest 这一个注解;

但是,如果用的是 org.junit.testorg.junit.before,测试类上面必须同时用 @runwith(springrunner.class)@springboottest(classes = myspringbootapplication.class)必须同时用!!!!!

import com.cyd.applicationstartup.myspringbootapplication;
import org.junit.jupiter.api.test;
import org.junit.jupiter.api.beforeeach;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;



@springboottest(classes = myspringbootapplication.class)
public class positionservicetest {

    @autowired
    private positionservice positionservice;

    @beforeeach
    public void init() {
    
    	// 对所有注解了@mock的对象进行模拟
        mockitoannotations.openmocks(this);
        system.out.println("初始化方法");
    }

    @test
    public void testgetstr() {

        string str = positionservice.getstr("刘亦菲");
        system.out.println("测试结果:" + str);

    }
}

测试结果:

在这里插入图片描述

(2)mock 测试

mock 测试需要自定返回结果,结果和方法返回结果类型一致。

语法如下:

mockito.when( 对象.方法名() ).thenreturn( 自定义结果 )

使用 mockito 模拟 bean 的单元测试代码示例如下:

import org.junit.jupiter.api.test;
import org.junit.jupiter.api.beforeeach;
import org.mockito.mock;
import org.mockito.mockito;
import org.springframework.boot.test.context.springboottest;


@springboottest(classes = myspringbootapplication.class)
public class positionservicetest {

    @mock
    private positionservice positionservice;

    @beforeeach
    public void init() {
        system.out.println("初始化方法");
    }

    @test
    public void testmockgetstr() {
        // 定义当调用mock positionservice 的 getstr() 方法,并且任意字符串参数时,就返回字符串 "刘亦菲"
        mockito.when(positionservice.getstr(mockito.anystring())).thenreturn("刘亦菲");

        // 定义当调用 mock positionservice 的 getstr() 方法,并且参数是字符串 "美女" 时,就返回字符串 "刘亦菲"
        mockito.when(positionservice.getstr("美女")).thenreturn("刘亦菲");
        system.out.println(positionservice.getstr("美女"));

        // 验证 positionservice 的 getstr()这个方法是否被调用过
        mockito.verify(positionservice).getstr("刘亦菲");
    }
}

注意:
代码中第一个 mockito.when 的参数用的是 mockito.anystring(),表示 任意字符串参数调用 getstr() 方法,就会返回字符串 “刘亦菲”;
第二个 mockito.when 的参数用的是字符串"美女",表示限制只有当参数是 "美女"时,才会返回 “刘亦菲”。
因此,在日常 mock 测试中,通常使用 mockito.any 作为参数。

(3)mock 测试常用方法

thenreturn 系列方法

① 定义当调用mock positionservice 的 getstr() 方法,并且任意字符串参数时,就返回字符串 “哈哈哈哈”:

mockito.when(positionservice.getstr(mockito.anystring())).thenreturn("哈哈哈哈");
system.out.println(positionservice.getstr("任意参数"));

表示任意值的参数如下图:

在这里插入图片描述

② 定义当调用 mock positionservice 的 getstr() 方法,并且限制参数只有是字符串 “美女” 时,才返回字符串 “刘亦菲”:

mockito.when(positionservice.getstr("美女")).thenreturn("刘亦菲");
system.out.println(positionservice.getstr("美女"));
thenthrow 系列方法

① 当调用 mock positionservice 的 getstr() 方法,输入的的参数是 字符串 “9” 时,抛出一个 runtimeexception:

mockito.when(positionservice.getstr("9")).thenthrow(new runtimeexception("mock throw exception"));
string str = positionservice.getstr("9"); //会抛出一个runtimeexceptio

测试结果:

在这里插入图片描述
② 如果方法没有返回值的话(即是方法定义为 public void mymethod() {…}),要改用 dothrow() 抛出 exception:

mockito.dothrow(new runtimeexception("mock throw exception when method is void")).when(positionservice).getvoid();
positionservice.getvoid(); //会抛出一个runtimeexception

测试结果:

在这里插入图片描述

verify 系列方法

① 检查调用 positionservice 的 getstr() 方法,、且参数为 “3” 的次数是否为1次:

mockito.verify(positionservice, mockito.times(1)).getstr(mockito.eq("3"));

② 验证调用顺序,验证 positionservice 是否先调用 getstr() 两次,并且第一次的参数是 “3”、第二次的参数是 “5”,然后才调用 getvoid() 方法:

inorder inorder = mockito.inorder(positionservice);
inorder.verify(positionservice).getstr("3");
inorder.verify(positionservice).getstr("5");
inorder.verify(positionservice).getvoid();
模拟对象有两种方式
  1. 对注解了 @mock 的对象进行模拟 mockitoannotations.openmocks(this);
  2. 对单个对象手动 mock :xxx= mockito.mock(xxx.class);
对 void 的方法设置模拟

positionservice 中有如下方法:

public void getvoidwithparam(string param){
        system.out.println("没有返回值");
    }

mock 测试方法:

		/*对void的方法设置模拟*/
        mockito.doanswer(invocationonmock -> {
            system.out.println("进入了mock");
            return null;
        }).when(positionservice).getvoidwithparam("param");

(4)mock 测试常用注解

  • 全部 mock
@mock
private servicea servicea;

这种方式,servicea中的所有方法都会被mock,并不会真正被调用到

  • 依赖注入

servicea 依赖了 servicec 和 daoa,使用injectmocks可以自动注入。

@injectmocks
private servicea servicea;
  • 真实调用
@spy
private servicec servicec;

这种方式,调用servicec的方法,会被真实调用。

五、mock 测试结合 java 反射综合案例

import java.lang.reflect.invocationtargetexception;
import java.lang.reflect.method;
import java.math.bigdecimal;
import java.text.simpledateformat;
import java.util.arraylist;
import java.util.date;

import org.junit.before;
import org.junit.test;
import org.junit.runner.runwith;
import org.mockito.injectmocks;
import org.mockito.mock;
import org.mockito.mockito;
import org.mockito.mockitoannotations;
import org.mockito.runners.mockitojunitrunner;
import org.springframework.test.util.reflectiontestutils;


@runwith(mockitojunitrunner.class)
public class mymockservicetest {

   
    @mock
    private customerdao             customerdao;

    @mock
    private mockdaoa     			mockdaoa;

    @mock
    private mockdaoc   				mockdaoc;

    @mock
    private mockdaod        		mockdaod;

    @mock
    private mockdaoe         		mockdaoe;

    @injectmocks
    mymockservice          			mymockservice;

    mocktestdatadto                 mocktestdatadto;

    @before
    public void init() {

        //        apollo 配置
        reflectiontestutils.setfield(mymockservice, "mockvalue", "58699dfr-1456984524");

        mockitoannotations.initmocks(this);

        mocktestdatadto = new mocktestdatadto();
        mocktestdatadto.setcallback("callback");

        policyrelatedinfo policyrelatedinfo = new policyrelatedinfo();
        policyrelatedinfo.setrelationtoappnt("1");
        mockito.when(mockdaoa.selectrelationbyparams(mockito.any()))
                .thenreturn(policyrelatedinfo);

        customer customer = new customer();
        insu.setphone("4654");
        insu.setsex("1");
        insu.setidtype("1");
        insu.setidno("1");
        insu.setname("张三");
        insu.setbirthday(new date());
        mockito.when(customerdao.selectbyprimarykey(mockito.anyint())).thenreturn(customer);

    }

    
    @test
    public void test() throws nosuchmethodexception, securityexception, illegalaccessexception,
            illegalargumentexception, invocationtargetexception, instantiationexception {

        arraylist<policydanger> getpolicydangerlist = new arraylist<>();

        
        policydanger policydanger1 = new policydanger();
        policydanger1.setispassflag("m");
        policydanger1.setdandercode("595648fd");
        policydanger1.settotalamnt(new bigdecimal(100.1223));
        

       
        policydanger policydanger2 = new policydanger();
        policydanger2.setispassflag("m");
        policydanger2.setdandercode("595648fd");
        policydanger2.settotalamnt(new bigdecimal(100.1223));

        getpolicydangerlist.add(policydanger1);
        getpolicydangerlist.add(policydanger2);
        mockito.when(mockdaoc.selectpolicydangerlist(mockito.any())).thenreturn(getpolicydangerlist);

        arraylist<province> provincelist = new arraylist<>();

        province province  = new province();
        province.setprovincecode("5894");
        province.setdutycode("5928d2");
        provincelist.add(province);
        mockito.when(mockdaod.selectpolicybyqueryparam(mockito.any())).thenreturn(provincelist);

        arraylist<user> userlist = new arraylist<>();
        user user = new user();
        user.setbuydate(new date());
        userlist.add(user);
        mockito.when(mockdaoe.selectuserbyqueryparam(mockito.any())).thenreturn(userlist);

        //        反射获得类
        mymockservice  hx = new mymockservice();
        class<? extends mymockservice> cls1 = hx.getclass();

        // 通过指定方法名称和参数类型的方法来获取method对象(注意: 如果方法名称不存在或参数类型不正确的话,会报错,不会返回null)
        // 注意:这里测试的是 private 修饰的私有方法,需要用 getdeclaredmethod
        // setuserinfo 是需要测试的方法名,后面为该方法需要的参数类型
        method method = cls1.getdeclaredmethod("setuserinfo", mocktestdatadto.class, integer.class, string.class,
                simpledateformat.class);
        method.setaccessible(true);

        //        执行方法
        method.invoke(mymockservice, mocktestdatadto, 1, "1", new simpledateformat());

    }
}

注意:

如果测试的类中有如下配置:

 @value("${mock.mapping.name}")
 private string  mockvalue;

测试代码中需要如下设置配置值:

 reflectiontestutils.setfield(mockservice, "mockvalue", "58699dfr-1456984524");
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com