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

07-11 1579阅读

文章目录

  • 一、什么是Mock
    • 1、Mock定义
    • 2、为什么使用
    • 3、常用的Mock技术
    • 4、Mokito中文文档
    • 5、集成测试和单元测试区别
    • 二、API
      • 1、Mockito的API
      • 2、ArgumentMatchers参数匹配
      • 3、OngoingStubbing返回操作
      • 三、Mockito的使用
        • 1、添加Maven依赖
        • 2、@InjectMocks、@Mock使用
        • 3、SpringbootTest 注解和 RunWith 注解在测试类的作用
        • 四、Mock 测试代码案例
          • 1、添加依赖
          • 2、编写业务代码
          • 3、Mock 测试
            • (1)常规测试
            • (2)Mock 测试
            • (3)Mock 测试常用方法
              • thenReturn 系列方法
              • thenThrow 系列方法
              • verify 系列方法
              • 模拟对象有两种方式
              • 对 void 的方法设置模拟
              • (4)Mock 测试常用注解
              • 五、Mock 测试结合 Java 反射综合案例

                一、什么是Mock

                1、Mock定义

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

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

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

                举个例子:

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

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

                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 环境依赖
                          
                            org.mockito
                            mockito-core
                            4.4.0
                          
                          
                          • SpringBoot 环境依赖

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

                                org.springframework.boot
                                spring-boot-starter-test
                            
                            

                            2、@InjectMocks、@Mock使用

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

                              直白的理解就是:

                              如图,实体类TUserServiceImpl通过注解 @Autowired注入了三个实体:TUserMapper、JdbcTemplate 和 NamedParameterJdbcTemplate。

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

                              如果想要测试TUserServiceImpl,那么test中,TUserMapper、JdbcTemplate 和 NamedParameterJdbcTemplate就要用@Mock注解创建Mock实例,而要被测试TUserServiceImpl就要用@InjectMocks注解创建实例,这个时候被@Mock注解创建的TUserMapper、JdbcTemplate 和 NamedParameterJdbcTemplate就被注入到通过 @InjectMocks注解创建的TUserServiceImpl实例中。如下图所示:

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

                              最后的大白话解释 !!!

                              你要测试哪个类(如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、添加依赖

                                     		
                                                org.springframework.boot
                                                spring-boot-starter-test
                                            
                                    

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

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

                                    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.Test 和 org.junit.jupiter.api.BeforeEach,是 jupiter.api 包下面的,此时测试类只用了 @SpringBootTest 这一个注解;

                                    但是,如果用的是 org.junit.Test 和 org.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);
                                        }
                                    }
                                    

                                    测试结果:

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

                                    (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("任意参数"));
                                    

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

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

                                    ② 定义当调用 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
                                    

                                    测试结果:

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

                                    ② 如果方法没有返回值的话(即是方法定义为 public void myMethod() {…}),要改用 doThrow() 抛出 Exception:

                                    Mockito.doThrow(new RuntimeException("mock throw exception when method is void")).when(positionService).getVoid();
                                    positionService.getVoid(); //会抛出一个RuntimeException
                                    

                                    测试结果:

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

                                    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 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 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 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
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]