【SpringBoot】解锁后端测试新境界:学习Mockito与MockMvc的单元测试魔法

news2024/12/25 22:38:05

文章目录

  • 前言:Java常见的单元测试框架
  • 一.Junit5基础
  • 二.SpringBoot项目单元测试
    • 1.添加依赖
    • 2.SpringBoot单元测试标准结构
    • 3.SpringBoot单元测试常用注解
  • 三.单元测试中如何注入依赖对象
    • 1.真实注入(@AutoWired、 @Resource)
    • 2.Mock注入
      • 2.1.前言
      • 2.2.Mock的概念
      • 2.3.实现原理和优点
    • 3.mock方法驱动
    • 4.Mock注解驱动
      • 1.@Spy
      • 2.@InjectMocks
      • 3. @MockBean
      • 4.thenReturn
      • 5.thenThrow
      • 6.doThrow
      • 7.行为验证
    • 5.非spring环境和spring环境注解驱动
      • @MockBean+@SpyBean+@Autowired
          • 使用mock
          • 使用Spy
    • 6. mock静态方法
    • 7.统计覆盖率
  • 四.拓展
    • 1.springboot设置虚拟属性
    • 2.模拟Web层(控制器)(GET请求/POST请求)
  • 五.私有方法的模拟
  • 六.总结

前言:Java常见的单元测试框架

JUnit:

  • JUnit是最早也是最著名的Java单元测试框架。它提供了丰富的断言方法,支持注解驱动测试,并与许多IDE(如IntelliJ IDEA和Eclipse)和构建工具(如Maven和Gradle)集成良好。
    • JUnit 5是JUnit的最新版本,它引入了全新的编程模型和扩展模型,使得编写和扩展测试更加灵活和强大。

TestNG:

  • TestNG是一个强大的Java测试框架,它允许你组织测试方法成组,并支持依赖测试(即一个测试依赖于另一个测试的结果)。
    • TestNG也提供了参数化测试的功能,允许你使用不同的数据集来运行相同的测试逻辑。

Mockito

  • Mockito是目前Java社区中最受欢迎的Mock框架之一。通常与JUnit一起使用。它提供了一个简单且灵活的API来创建和配置模拟对象。
  • Mockito支持创建模拟对象、定义模拟对象的行为、验证方法调用等。它还提供了许多高级功能,如参数匹配、部分模拟、验证调用顺序等。

PowerMock

  • PowerMock扩展了EasyMock和Mockito的功能,支持对静态方法、构造函数、私有方法等进行模拟
  • PowerMock特别适用于那些难以使用传统Mock框架进行模拟的场景,例如使用了静态方法或私有方法的代码。

AssertJ

  • AssertJ是一个流式的Java断言库,它提供了更自然和富有表达力的方式来编写断言代码。与JUnit等测试框架结合使用,可以使测试代码更加清晰和易于理解。

Hamcrest

  • Hamcrest是一个匹配器库,它提供了丰富的匹配器来构建复杂的断言条件Hamcrest与JUnit等测试框架配合使用,可以使断言更加灵活和强大。

一.Junit5基础

学习单元测试和断言前请先了解 【Java基础】使用Junit5进行单元测试 基础

在这里插入图片描述

二.SpringBoot项目单元测试

1.添加依赖

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

spring-boot-starter-test中包含了junit和mockito等依赖
在这里插入图片描述

相关依赖

  • junit:标准的单元测试Java应用程序
  • Spring Test & Spring Boot Test : 对Spring Boot应用程序的单元测试提供支持
  • Mockito:Java mocking框架,用于模拟任何Spring管理的Bean,比如在单元测试中模拟一个第三方系统Service接口返回的数据,而不会去真正调用第三方系统;
  • assertj:断言库,提供多种比较期望值与测试返回值的方法;
  • JSONassert:对JSON对象或者JSON字符串断言的库。
  • Hamcrest:它提供了丰富的匹配器来构建复杂的断言条件Hamcrest与JUnit等测试框架配合使用,可以使断言更加灵活和强大。

2.SpringBoot单元测试标准结构

@DisplayName("TestDemo测试类")  //起别名
@SpringBootTest //1.类上添加注解,加载ApplicationContext,启动spring容器。
@AutoConfigureMockMvc //2.启动mockMVC测试
@Transactional //3.开启事务管理
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)//开启测试类的执行顺序按@order配置的优先级执行
public class TestDemo {
    @Test
    @Order(3)//第3个执行
    public void test1() {
        int a = 1;
        Assertions.assertNotEquals(1, a);//判断二者是否不相等
    }
    @Test
    @Order(2)//第2个执行
    public void test2() {
        int a = 1;
        Assertions.assertNotEquals(1, a);//判断二者是否不相等
    }
    @Test
    @Order(1)//第一个执行
    public void test3() {
        int a = 1;
        Assertions.assertNotEquals(1, a);//判断二者是否不相等
    }
}

执行结果
在这里插入图片描述

一 般情况下,使用@SpringBootTest后,Spring将加载所有被管理的bean基本等同于启动了整个springboot服务,此时便可以开始功能测试。

  • 可以通过webEnvironment参数启动的Web环境对应的端口,springboot提供了4种设置如下:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    
    • MOCK:默认值,该类型提供一个mock环境,可以和@AutoConfigureMockMvc@AutoConfigureWebTestClient搭配使用,开启Mock相关的功能。注意此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web服务端口
    • RANDOM_PORT:启动一个真实的web服务,监听一个随机端口。(建议)
    • DEFINED_PORT:启动一个真实的web服务,监听一个定义好的端口(从application.properties读取)。
    • NONE:启动一个非web的ApplicationContext,既不提供mock环境,也不提供真实的web服务。

3.SpringBoot单元测试常用注解

在这里插入图片描述

  • @Mock :是Mockito.mock()方法的简写。创建的是全部mock的对象,即在对具体的方法打桩(即创建模拟对象)之前,mock对象的所有属性和方法全被置空(0或null)

    • @mock注解需要搭配MockitoAnnotations.openMocks(testClass)方法一起使用.
  • @Spy:是Mockito.Spy()方法的简写。被 spy 的对象,调用其方法时默认会走真实方法。,有返回值的调用真实方法并返回真实值

    • 如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。定义了mock方法的则执行mock(即虚假函数);默认生成后所有依赖的对象都会null,且要一个无参构造
  • @InjectMocks :将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。
    注入方式有多种,mockito 会按照下面的顺序尝试注入:

    • 构造函数注入
    • 设值函数注入(set函数)
    • 属性注入
    //类1
    public class HttpService {
        public int queryStatus() {
            // 发起网络请求,提取返回结果
            // 这里用随机数模拟结果
            return new Random().nextInt(2);
        }
    }
    
    //类2
    public class ExampleService {
        private HttpService httpService;
    
        public String hello() {
            int status = httpService.queryStatus();
            if (status == 0) {
                return "你好";
            }
            else if (status == 1) {
                return "Hello";
            }
            else {
                return "未知状态";
            }
        }
    }
    
    public class ExampleServiceTest {
        @InjectMocks // 将@Mock httpService主动注入ExampleService
        private ExampleService exampleService = new ExampleService();
        @Mock
        private HttpService httpService;
    
        @Test
        public void test01() {
            MockitoAnnotations.initMocks(this);
    
            when(httpService.queryStatus()).thenReturn(0);
            Assert.assertEquals("你好", exampleService.hello());
        }
    }
    
  • @MockBean : Spring Boot 中的注解。我们可以使用 @MockBean mock 对象添加到 Spring 应用程序上下文中。该 mock 对象将替换应用程序上下文中任何现有的相同类型的 bean。如果应用程序上下文中没有相同类型的 bean,它将使用 mock 的对象作为 bean 添加到上下文中。

  • @SpyBean:同上。

三.单元测试中如何注入依赖对象

1.真实注入(@AutoWired、 @Resource)

  • 对DAO层(Service层同理)
    在这里插入图片描述
    在这里插入图片描述
  • @Autowired直接注入的方式是基于真实环境的,会真实操作数据库,如果在单元测试中不想改变数据数据库中的值,不能使用直接注入的方法

可以在类上再添加这两个注解,通过@Transactional+@Rollback(true)可以知道调用了数据库,对其操作进行回滚

  • 但是如果项目中使用了@Component注解(在SpringBoot项目启动的时候就会跟着实例化/启动),@Component注解的类里有多线程方法,那么在执行单元测试的时候,由于多线程任务的影响,就可能对数据库造成了数据修改,

    • 即使使用了事务回滚注解@Transactional。(我在百度上看到的,没找到具体的测试方法,所以没试)
@Transactional
@Rollback(true) // 事务自动回滚,默认是true。可以不写

2.Mock注入

2.1.前言

在这里插入图片描述

2.2.Mock的概念

  1. 所谓的mock就是创建一个类的虚拟对象,在测试环境中,用来替换掉真实的对象,以达到2个目的:

    • 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
    • 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
  2. 使用Mock之前,需要在@Before或@BeforeClass对应的方法中添加如下,表示 添加mock注解初始化

    MockitoAnnotations.initMocks(this);
    
  3. 另外需要补充以下几个常用的测试注解:

    • @InjectMocks:通过创建一个实例,它可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。

    • @Mock:该对象下对函数的调用均执行mock(即虚假函数),不执行真正具体操作。

    • @Spy:对函数的调用均执行真正部分。

  4. Mockito中的@Mock和@Spy都可用于拦截那些尚未实现不期望被真实调用的对象和方法,并为其设置自定义行为。

    • 二者的区别在于Mock不真实调用,Spy会真实调用
Mockito 默认是“不支持静态方法,可使用 PowerMockMockito 支持静态方法(新增依赖)

2.3.实现原理和优点

实现原理使用Stub(桩)技术动态的替换原程序的功能。

  • 直接跑Java代码,不需要启用Spring容器及连接数据库,模拟一切操作数据库的步骤,不执行任何SQL,也可以模拟任何返回值
    • Stub(桩)技术:在单元测试中中用于替代实际对象或方法的技术,主要是提供一个预定义的、固定的返回值或调用,以便在测试中模拟实际对象或方法的调用。

使用Mock的优点:

  • 可以完全脱离数据库
  • 只针对某一个小方法(一个小的单元)来测试,测试过程中,不需要启动其他的东西,不免其他因素可能产生的干扰

3.mock方法驱动

  • Mockito.mock(xxx.class) 创建mock对象

  • Mockito.mock(classToMock,defaultAnswer) 使用默认Answer模拟对象

    import org.junit.Assert;
    import org.junit.Test;
    import org.mockito.Mockito;
    import java.util.Random;
    
    public class MockitoFirstDemo {
        @Test
        public void test() {
         	//mock了一个Random对象
            Random mockRandom = Mockito.mock(Random.class);
    
            System.out.println("mock前:"+mockRandom.nextInt());
            Assert.assertEquals(0, mockRandom.nextInt());//未进行打桩,每次返回值都是0
    
    		 //设置random.nextInt()虚拟值为100
            Mockito.when(mockRandom.nextInt()).thenReturn(100);  // 进行打桩操作,指定调用 nextInt 方法时,永远返回 100
            System.out.println("mock后:"+mockRandom.nextInt());
    
            Assert.assertEquals(100, mockRandom.nextInt());
        }
    }
    

    ·在这里插入图片描述

  • Mockito.doThrow(toBeThrown).when(mock).[method] 模拟抛出异常

    //如果mockRandom对象调用nextInt()方法 抛出空指针异常
    Mockito.doThrow(new NullPointerException()).when(mockRandom).nextInt();
    mockRandom.nextInt();
    
  • Mockito.when(methodCall).thenReturn(value) 模拟方法调用返回值

  • Mockito.doReturn(toBeReturned).when(mock).[method] 模拟方法调用返回值(直接执行不判断)

  • Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) 模拟多次方法调用返回值,触发时第一次返回value1,第n次都返回value2

     //1.模拟nextInt方法调用返回100
     Mockito.when(mockRandom.nextInt()).thenReturn(100);
     //2.触发时方法调用nextInt第一次返回101,nextInt的第n次都返回102(可以一直设置值)
     Mockito.when(mockRandom.nextInt()).thenReturn(101).thenReturn(102);
     
    //3.模拟nextInt方法调用返回100 (同第一种)        
     Mockito.doReturn(100).when(mockRandom).nextInt();
     System.out.println(mockRandom.nextInt());//返回100
    
  • Mockito.when(methodCall).thenAnswer(answer)) 自定义模拟方法的返回值,可以根据方法的传参定义方法的返回

  • Mockito.doAnswer(answer).when(methodCall).[method] 自定义模拟方法的返回值,可以根据方法的传参定义方法的返回

    @Test
        public void test() {
            // mock一个对象
            HashMap mockMap =  Mockito.mock(HashMap.class);
            mockMap.put("key1", "value1");
            mockMap.put("key2", "value2");
            Mockito.when(mockMap.get(ArgumentMatchers.anyString())).thenAnswer(
                    new Answer() {
                        public Object answer(InvocationOnMock invocation) {
                            Object[] args = invocation.getArguments();
                            Object mock = invocation.getMock();
                            String key = (String) args[0];
                            
                            //修改key=key1的返回值
                            if (key.equals("key1")) {
                                return "called with arguments: " + Arrays.toString(args);
                            }
                            //修改key=key2的返回值
                            if (key.equals("key2")) {
                                return "called with arguments: " + Arrays.toString(args);
                            }
    
                            return "error key";
                        }
                    });
    
    
            System.out.println(mockMap.get("key1"));  // called with arguments: [key1]
            System.out.println(mockMap.get("key2"));  // called with arguments: [key2]
            System.out.println(mockMap.get("key3"));  //error key
        }
    
  • Mockito.verify(mock) 验证对象的方法调用是否发生

    		//创建mock对象
            ArrayList list = Mockito.mock(ArrayList.class);
    
            list.add(1);
            list.add(2);
    
            Mockito.verify(list).add(1);//验证通过
            Mockito.verify(list).add(5);//验证未通过,因为没有执行过该操作
    
    
  • Mockito.spy(Object) 用spy监控真实对象,设置真实对象行为

            //虚假调用
            ExampleService mockExample = Mockito.mock(ExampleService.class);
            int num = mockExample.add(1, 1);
            System.out.println("虚假调用>>>"+num);
            //返回虚假调用>>>0
    
            ExampleService spyExample = Mockito.spy(ExampleService.class);
            num = spyExample.add(2, 2);
            System.out.println("真实调用>>>"+num);
            //真实调用方法,参数a=2,参数b=2
            //真实调用>>>4
    

    在这里插入图片描述

    在这里插入图片描述

  • when().Return() 与 doReturn() 设置方法的返回值

    Mockito.when(mock.someMethod("some args")).Return("result");
    Mockito.doReturn("result").when(mock).someMethod("some arg");
    
  • when().thenthrow() 与 doThrow() 让方法抛出异常

    // 只针对返回值非void的函数
    Mockito.when(mock.someMethod("some args")).thenthrow(new Exception("自定义异常"));
    
    // 通用
    Mockito.doThrow(new Exception("自定义异常"))
        .when(mock)
        .someMethod("some arg");
    
    
  • doNothing() 让void函数什么都不做

    Mockito.doNothing().when(mock).someMethod("some args");
    
  • doAnswer()自定义方法处理逻辑

         // 自定义返回值thenAnswer()
         when(mock.someMethod(anyString())).thenAnswer(
             new Answer() {
                 public Object answer(InvocationOnMock invocation) {
                     Object[] args = invocation.getArguments();
                     Object mock = invocation.getMock();
                     return "called with arguments: " + Arrays.toString(args);
                 }
         });
        
         //Following prints "called with arguments: [foo]"
         System.out.println(mock.someMethod("foo"));
    
  • thenCallRealMethod()调用 spy 对象的真实方法

    Mockito.when(spy.someMethod("some args")).thenCallRealMethod();
    
    Mockito.doCallRealMethod().when(spy).someMethod("some arg");
    
  • 使用then、thenAnswer 自定义方法处理逻辑

    • 实现 Answer 接口的对象,在该对象中可以获取调用参数,自定义返回值
         // 自定义返回值thenAnswer()
         when(mock.someMethod(anyString())).thenAnswer(
             new Answer() {
                 public Object answer(InvocationOnMock invocation) {
                     Object[] args = invocation.getArguments();
                     Object mock = invocation.getMock();
                     return "called with arguments: " + Arrays.toString(args);
                 }
         });
        
         //Following prints "called with arguments: [foo]"
         System.out.println(mock.someMethod("foo"));
    
    
        @Test
        void testDemo02() {
          Mockito.when(studentService.getStudentByUserName("张三")).thenAnswer(
              (Answer<Student>) invocationOnMock -> new Student("赵六","13215522144","河南省")
          );
          Student student = studentService.getStudentByUserName("张三");
          // prints: Student{username='赵六', phone='13215522144', address='河南省'}
          System.out.println(student.toString());
        }
    
  • reset()方法,可以重置之前自定义的返回值和异常

    import org.junit.Assert;
    import org.junit.Test;
    import static org.mockito.Mockito.*;
    public class MockitoDemo {
        static class ExampleService {
            public int add(int a, int b) {
                return a+b;
            }
        }
        @Test
        public void test() {
            ExampleService exampleService = mock(ExampleService.class);
    
            // mock 对象方法的默认返回值是返回类型的默认值
            Assert.assertEquals(0, exampleService.add(1, 2));
    
            // 设置让 add(1,2) 返回 100
            when(exampleService.add(1, 2)).thenReturn(100);
            Assert.assertEquals(100, exampleService.add(1, 2));
    
            // 重置 mock 对象,add(1,2) 返回 0
            reset(exampleService);
            Assert.assertEquals(0, exampleService.add(1, 2));
        }
    
    

4.Mock注解驱动

  • @Mock 注解可以理解为对 Mockito.mock()的一个替代
  • 使用该注解时,要使用MockitoAnnotations.initMocks​ 方法,让注解生效
    • 旧版的是initMocks,新版的是openMocks
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.when;

public class MockitoTwoDemo {
    @Mock
    private Random random;

    @Before
    public void before() {
        // 初始化mock,让注解生效(新版)
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void test() {
        //设置random.nextInt()虚拟值为100
        when(random.nextInt()).thenReturn(100);
        System.out.println("mock后:"+random.nextInt());
        Assert.assertEquals(100, random.nextInt());
    }
}

在这里插入图片描述

也可以用MockitoJUnitRunner​来代替MockitoAnnotations.openMocks

1.@Spy

mock()方法与spy()方法的不同:

  • 被spy的对象会走真实的方法,而mock对象不会
  • spy方法的参数是对象实例,mock的参数是class

2.@InjectMocks

  • @InjectMocks由mock框架管理,只能将 @Mock​、@Spy​ 修饰的对象自动注入到@InjectMocks​修饰的对象中

    @Mock
    AService aService;
    
    @InjectMocks
    AController aController;  //这里会注aService
    
    @Autowired
    AController aController;//这里不会注aService
    
    class BController{
        AService aService;
    }
    
  • 如果想一个spring对象注入mock框架的对象,可通过@InjectMocks桥接。

    @Mock
    AService aService;
    
    @Autowired
    @InjectMocks
    AController aController;//这里会注入aService
    
    

3. @MockBean

  • @MockBean和@SpyBean由spring管理,会替换上下文相同对象。

    @MockBean
    AService aService;
    
    @Autowired
    AController aController; //这里会注入aService
    

4.thenReturn

thenReturn 用来指定特定函数和参数调用的返回值

  • thenReturn 中可以指定多个返回值在调用时返回值依次返回。 若调用次数超过返回值的数量,再次调用时返回最后一个返回值

doReturn 的作用和 thenReturn 相同,但使用方式不同:

//mockRandom.nextInt()返回虚拟值1
Mockito.when(mockRandom.nextInt()).thenReturn(1);//返回值为1
//mockRandom.nextInt()依次返回虚拟值1 2 3
Mockito.when(mockRandom.nextInt()).thenReturn(1, 2, 3);

//mockRandom.nextInt()的返回值设置为1
Mockito.doReturn(1).when(random).nextInt();

5.thenThrow

  • thenThrow 用来让函数调用抛出异常。(可搭配try catch使用)

    • 可以指定多个异常。在调用时异常依次返回若调用次数超过异常的数量,再次调用时抛出最后一个异常。
//调用mockRandom.nextInt()抛出RuntimeException异常
Mockito.when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));
//调用mockRandom.nextInt()依次抛出RuntimeException异常
Mockito.when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));
    @Test
    public void testThenThrow() {
        Random mockRandom = mock(Random.class);
        //调用mockRandom.nextInt()抛出RuntimeException异常
        Mockito.when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));

        try {
            mockRandom.nextInt();
            Assert.fail();//上一行会抛出异常,到catch中去,走不到这里
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常1", ex.getMessage());
        }

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常2", ex.getMessage());
        }
    }

在这里插入图片描述

6.doThrow

  • 对应返回类型是 void 的函数,thenThrow 是无效的,要使用 doThrow。也可以用 doThrow 让返回非void的函数抛出异常
doThrow(new RuntimeException("异常")).when(exampleService).hello();

// 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));
doThrow(new RuntimeException("异常")).when(random).nextInt();

7.行为验证

  • 使用 verify 可以校验 mock 对象是否发生过某些操作,配合 time 方法,可以校验某些操作发生的次数
        //是否调用过一次
        Mockito.verify(spy).hasReturnAndArgs(Mockito.anyString());
        //是否调用过N次
        Mockito.verify(spy,times(1)).hasReturnAndArgs(Mockito.anyString());
        //没有被调用,相当于 times(0)
        Mockito.verify(spy,never()).hasReturnAndArgs(Mockito.anyString());
        //atLeast(N) 至少被调用 N 次
        //atLeastOnce() 相当于 atLeast(1)
        //atMost(N) 最多被调用 N 次

5.非spring环境和spring环境注解驱动

  • 非spring环境:@Mock+@Spy+@InjectMocks
  • spring环境:@MockBean+@SpyBean+@Autowired

@MockBean+@SpyBean+@Autowired

spring环境使用@MockBean+@SpyBean+@Autowired,为测试主体类部分打桩考虑使用@SpyBean, 为外部依赖打桩,考虑使用@MockBean

//业务层
@Service
public class AService {
	public String hasReturnAndArgs(String str){
	    return "10";
	}
	public String hasReturn(){
	    return "10";
	}
	public void hasArgs(String str){
	    System.out.println(1000);
	}
	public void noArgs(){
	    System.out.println(1000);
	}
}

//控制层
@RestController
public class AController {
    @Autowired //注入aService
    private AService aService;
    
	public String hasReturnAndArgs(String str){
	    return aService.hasReturnAndArgs(str);
	}
	public String hasReturn(){
	    return aService.hasReturn();
	}

	public void hasArgs(String str){
	    aService.hasArgs(str);
	}
	public void noArgs(){
	    aService.noArgs();
	}
}

使用mock
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ServiceTest {
    @Before
    public void before() {
        // 启用 Mockito 注解
        MockitoAnnotations.initMocks(this);
    }
    
    @Mock //mock AService
    AService aService;
    @InjectMocks  //将 @Mock​、@Spy​ 修饰的对象自动注入到@InjectMocks​修饰的对象中
    AController aController;
    
    @Test
    public void test() {
        //1.不调用真实方法,默认返回null
        String value = aService.hasReturnAndArgs("10");
        Assert.assertEquals(value, null);

        //2.打桩
        //当传参是10L时,返回 30
        Mockito.when(aService.hasReturnAndArgs("10")).thenReturn("30");
        //当传参是20L时,真实调用
        Mockito.when(aService.hasReturnAndArgs("20")).thenCallRealMethod();
        //当传参是30L时,抛出异常
        Mockito.when(aService.hasReturnAndArgs("30")).thenThrow(new Exception("test error"));
		
		//断言方法传参为10时是否等于 30,
        Assert.assertEquals(aService.hasReturnAndArgs("10"), "30");
        
        //当传参是20L时,真实调用方法,内部mock对象调用的也是mock方法
        Assert.assertNotEquals(aService.hasReturnAndArgs("20"), "30");
        try {
            Assert.assertNotEquals(aService.hasReturnAndArgs("30"), "30");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        //3.注入对象
        Assert.assertEquals(aController.hasReturnAndArgs("10"), "30");
    }
}
使用Spy
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ServiceTest {
    @Before
    public void before() {
        // 启用 Mockito 注解
        MockitoAnnotations.initMocks(this);
    }
 
    @Spy
    AService spy;
    
    @Test
    public void test() {
        //AService spyTemp = new AService();
        //AService spy = Mockito.spy(spyTemp);

        //1.调用真实方法
        Assert.assertEquals(spy.hasReturnAndArgs("20"), "10");

        //2.打桩
        Mockito.doReturn("30").when(spy).hasReturnAndArgs("20");
        Assert.assertEquals(spy.hasReturnAndArgs("20"), "30");
        //验证是否被调用了一次
        Mockito.verify(spy,times(1)).hasReturnAndArgs("20");


        //设置任何hasReturnAndArgs调用都返回30
        Mockito.doReturn("30").when(spy).hasReturnAndArgs(Mockito.anyString());
        Assert.assertEquals( spy.hasReturnAndArgs("-2"), "30");
        Mockito.verify(spy,times(2)).hasReturnAndArgs(Mockito.anyString());



        //不支持这样
        Mockito.when(spy.hasReturnAndArgs("20")).thenReturn("10");
        Assert.assertEquals(spy.hasReturnAndArgs("20"), "10");
    }
}

使用spring集成

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ServiceTest {
    @Before
    public void before() {
        // 启用 Mockito 注解
        MockitoAnnotations.initMocks(this);
    }
  
    @SpyBean
    private AService spy;
    @Autowired
    AController aController;
    @Test
    public void test() {
        //调用真实方法
        Assert.assertEquals(spy.hasReturnAndArgs("20"), "10");
        Mockito.doReturn("30").when(spy).hasReturnAndArgs(Mockito.anyString());
        Assert.assertEquals(spy.hasReturnAndArgs("20"), "30");
        Mockito.verify(spy,times(1)).hasReturnAndArgs(Mockito.anyString());
        Assert.assertEquals(aController.hasReturnAndArgs("20"), "30");
    }
}

6. mock静态方法

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>4.0.0</version>
        </dependency>

        MockitoAnnotations.initMocks(this);
        Mockito.mockStatic(XXX.class).when(XXX::getXXX)
                .thenReturn("xxx");
                
                //如果用多次需要关闭
        try(MockedStatic<XXX> xx= Mockito.mockStatic(XXX.class)) {
            xx.when(() -> A.b(params)).thenReturn(null);
        }

7.统计覆盖率

在这里插入图片描述
红色为尚未覆盖的行,绿色为覆盖的行。class,method,line分别表示类/方法/行代码测试覆盖率
在这里插入图片描述

四.拓展

1.springboot设置虚拟属性

假如我springboot项目有一个application.yml文件

test:
  prop: testValue1

当编写单元测试测试的时候,在不修改源码的情况下,想改变prop属性为testValue2,该怎么办呢?

  • 加载测试临时属性可以通过注解@SpringBootTest的propertiesargs属性进行设定,作用域仅限于当前测试用例
    @Slf4j
    @SpringBootTest(properties = {"test.prop=testValue2"})
    class PropertiesAndArgsTest {
        @Value("${test.prop}")
        private String msg;
        @Test
        void test01() {
            log.info(msg);
        }
    }
    

2.模拟Web层(控制器)(GET请求/POST请求)

springMVC框架的测试中,一般采用mockMvc+Mockito的组合来进行mock模拟测试,即:Mockito模拟服务层的方法, MockMvc 来模拟发起HTTP请求

  • 切片测试:指用mockmvc测试controller层,模拟返回service层的值,将层与层间的联系断开。
  • 集成测试:指用mockmvc测试controller层,但不间隔service层。将controller层和service层集合起来测试。

在单元测试中对controller层功能进行测试,必须模拟一个真实的web环境,具体步骤如下:

  1. 测试类中启动web环境

    • 每一个springboot的测试类都需@SpringBootTest注解,通过webEnvironment属性设置在测试用例中启动web环境,具体如下:

      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      public class WebTest {
      }
      
  2. 测试类中发送请求

    • 先提前写一个controller,用于后面的get和post测试
      @RestController
      @RequestMapping("/user")
      @Slf4j
      public class TestController {
          /**
           * post请求
           * @param param json数据
           * @return json数据
           */
          @PostMapping("/post")
          public Map<String, Object> post(@RequestBody Map<String, Object> param) {
              log.info(">>>>>>>>>post user:{}", param);
      
              Map<String, Object> data = new HashMap<>();
              data.put("id", 2);
              data.put("username", "post");
              return data;
          }
      
      
          /**
           * get请求,接收json以及 地址栏参数
           * @param param  json数据
           * @param id 地址栏参数
           * @return json数据
           */
          @GetMapping("/get")
          public Map<String, Object> get(@RequestBody Map<String, Object> param,@RequestParam("id") Integer id) {
              log.info(">>>>>>>>>get user:{},id={}", param,id);
      
              Map<String, Object> data = new HashMap<>();
              data.put("id", 1);
              data.put("username", "get");
              return data;
          }
      }
      
  3. 在测试类中通过@AutoConfigureMockMvc开启web虚拟调用功能

    • 注入MockMvc对象,通过MockMvc对象可以发送虚拟请求,模拟web请求调用过程
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    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.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    
    //1.测试类中启动web环境
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    //日志调用
    @Slf4j
    //2.开启虚拟MVC调用
    @AutoConfigureMockMvc
    public class WebTest {
        //3.注入MockMVC
        @Autowired
        MockMvc mockMvc;
    
    
        /**
         * 测试post请求
         *
         * @throws Exception
         */
        @Test
        void testUserPost() throws Exception {
            MvcResult mvcResult  = mockMvc.perform(MockMvcRequestBuilders.post("/user/post")
                    //g请求参数为json
                    .content("{\"username\":\"oyang\",\"password\":\"123456\"}")
                    .header("Authorization", "Bearer ...")
                    .contentType(MediaType.APPLICATION_JSON)
            )
                    //预期响应状态为200
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    // 可以取出 json的字段值,判断code是否为0  响应结果:
                    .andExpect(MockMvcResultMatchers.jsonPath("$.code").value("0"))
                    .andReturn();
            //{"code":0,"msg":"success","time":"20240327150240","data":{"id":2,"username":"post"},"requestId":null}
            log.info(">>>>>mock响应结果:{}",  mvcResult.getResponse().getContentAsString());
        }
    
    
        /**
         * 测试get请求
         *
         * @throws Exception
         */
        @Test
        void testUserGet() throws Exception {
            int id = 111;
            MvcResult mvcResult  = mockMvc.perform(MockMvcRequestBuilders.get("/user/get")
                    //get请求参数在url上
                    .param("id", "" + id)
                    //get请求参数为json
                    .content("{\"username\":\"oyang\",\"password\":\"123456\"}")
                    .header("Authorization", "Bearer ...")
                    .contentType(MediaType.APPLICATION_JSON)
            )
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn()
                    ; //预期响应状态为200
    
            //{"code":0,"msg":"success","time":"20240327150240","data":{"id":1,"username":"get"},"requestId":null}
            log.info(">>>>>mock响应结果:{}",  mvcResult.getResponse().getContentAsString());
        }
    
    
    • 最终web测试需要将预计值与真实值的比对才能确认测试结果是否通过

五.私有方法的模拟

  1. 待类中私有方法,可以用反射的方式进行测试

    • spring框架中使用封装的反射API,来设置private的属性:

       ReflectionTestUtils.setField(Object targetObject, String name, @Nullable Object value);
      
       //或者
       Field field = ReflectionUtils.findField(targetClass, name, type);
       if (field == null) {
         }
       ReflectionUtils.makeAccessible(field);
       ReflectionUtils.setField(field, targetObject, value);
      
    • 如果是非spring框架,也可以直接使用Java原生反射API:

              Field field = target.getClass().getDeclaredField(fieldName);
              field.setAccessible(true); //改成可访问,不管现有修饰
              field.set(target, value);
      
  2. maven打包时使用命令打包时跳过test

    mvn deploy -f pom_http.xml-jar -Dmaven.test.skip=true
  3. Mockito 默认是不支持静态方法,可使用 PowerMock 让 Mockito 支持静态方法(新增依赖)

六.总结

  • Mockito可以轻松集成到现有的Spring Boot项目中,无论是对于简单的单元测试还是更复杂的集成测试。
  • 通过使用Mockito,可以模拟服务层、存储库、REST客户端等组件,而无需依赖实际的实现。来减少测试对外部系统的依赖,模拟异常情况和边缘用例,从而确保代码在各种环境下的稳健性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1666889.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构复习指导之树、森林

文章目录 树、森林 考纲内容 复习提示 1.树的存储结构 1.1双亲表示法 1.2孩子表示法 1.3孩子兄弟表示法 2.树、森林、与二叉树的转换 2.1树转换为二叉树 2.2森林转换为二叉树 2.3二叉树转换为森林 3.树和森林的遍历 3.1树的遍历 3.2森林的遍历 树、森林 考纲内容…

开源推荐榜【FunClip是一款完全开源、本地部署的自动化视频剪辑工具】

FunClip是一款完全开源、本地部署的自动化视频剪辑工具&#xff0c;通过调用阿里巴巴通义实验室开源的FunASR Paraformer系列模型进行视频的语音识别&#xff0c;随后用户可以自由选择识别结果中的文本片段或说话人&#xff0c;点击裁剪按钮即可获取对应片段的视频&#xff08;…

zookeeper安装集群模式

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 ZooKeeper是一个分…

JeeSite V5.7.0 发布,Java快速开发平台,Vite5、多项重构重磅升级

JeeSite V5.7.0 发布&#xff0c;Java快速开发平台&#xff0c;Vite5、多项重构重磅升级 升级内容 新增 参数配置 IP 地址黑白名单过滤器动态参数 新增 侧边栏是否展开第一个菜单的开关 first-open 新增 AesTypeHandler 处理字段数据加密解密或脱敏 新增 JsonTypeHandler …

247 基于matlab的梁的振型仿真

基于matlab的梁的振型仿真。利用有限元理论&#xff0c;求二维梁的固有频率和振型。短边固定&#xff0c;给定长度、横截面积&#xff0c;弹性模量及材料密度已知。并对比理论计算结果进行分析。各参数自己设定。程序已调通&#xff0c;可直接运行。 247 梁的振型仿真 固有频率…

Linux环境下parted工具使用

在工作中&#xff0c;我们经常会遇到大于分区大于2T的磁盘&#xff0c;由于系统盘最大不能超2T&#xff0c;我们会在做raid时将划分VD来进行装系统&#xff0c;但系统自动安装后无法将磁盘全部识别出来&#xff0c;管理员有时会要求手动对分区进行挂载&#xff0c;这个文档介绍…

收放卷伺服控制系统详细算法介绍(电子齿轮+张力PID卷绕轴控制功能块)

收放卷控制系统涉及的内容非常多,这里我们介绍全伺服系统利用电子齿轮指令实现主从轴的比例随动速度控制,收放卷控制算法介绍常用链接如下 1、收放卷+排线控制 收放卷+排线控制系统框图-CSDN博客文章浏览阅读24次。1、收放卷前馈量计算FC收放卷前馈量计算FC(CODESYS ST源代…

将python库下载到本地安装—Pypi官网wheel版本选择详解—小白详解版

python库—本地安装文件下载&#x1f680; 在项目中需要在内网环境下配置python的环境&#xff0c;因此需要将用于安装python库的文件下载到本地传到内网环境当中然后再安装&#xff0c;通过这契机我开始了解了一下如何离线下载安装python的第三方库&#xff0c;以及配置本地的…

【Docker】Ubuntu下Docker的基本使用方法与常用命令总结

【Docker】docker的基本使用方法 镜像image与容器container的关系基本命令- 查看 Docker 版本- 拉取镜像- 查看系统中的镜像- 删除某个镜像- 列出当前 Docker 主机上的所有容器&#xff0c;包括正在运行的、暂停的、已停止的&#xff0c;以及未运行的容器- 列出当前 Docker 主机…

Matlab 验证 复数的幂计算规则

复数的幂计算规则 close all a9; b0:0.1:5;result1 exp(1j*2*pi*a.*b); result2 (exp(1j*2*pi*a)).^b; idxfind(result1result2); b_idxb(idx);figure plot(b,angle(result1(:)),-r*) hold on plot(b,angle(result2(:)),bo) grid on

C++内存管理(1)

目录 1.new用法说明 2.new/delete在栈里面的运用 3.operator new/operator delete函数 4.构造函数的显式调用 5.malloc&&new&&free&&delete区别 1.new用法说明 &#xff08;1&#xff09;在C语言阶段&#xff0c;我们无论是为数组开辟空间&#x…

Disk Map for Mac,让您的Mac更“轻”松

还在为Mac磁盘空间不足而烦恼吗&#xff1f;Disk Map for Mac来帮您轻松解决&#xff01;通过独特的TreeMap视觉显示技术&#xff0c;让您一眼就能看出哪些文件和文件夹占用了大量空间。只需简单几步操作&#xff0c;即可快速释放磁盘空间&#xff0c;让您的Mac更“轻”松。快来…

STL-Hashtable

hashtable hashtable是通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系&#xff0c;这样在查找的时候就可以很快的找到该元素。 哈希函数 哈希函数的定义域必须包括需要存储的全部关键码&#xff0c;而如果散列表允许有m个地址时&#xff0c…

Web UI自动化测试--PO模式

没有PO实现的测试用例的问题: 重用性低:登录功能重复可维护性差:数据和代码混合可读性差:元素定位方法杂乱(id、xpath、css混杂)可读性差:不易识别操作的含义(特别是css和xpath语法)可维护性差:如果某个元素的属性改了,你要更改多次PO(Page Object Model)页面对象模型…

Linux 第二十八章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

C# SolidWorks 二次开发 -从零开始创建一个插件(3) 发布插件

五一节过完了吧&#xff0c;该上班学习了吧&#xff1f; 如何把自己开发好的程序优雅的给别人使用。 今天我们来简单讲解一下&#xff0c;这个之前不少粉丝咨询过相关问题&#xff0c;自己开发好的东西&#xff0c;如何给同事或者其它人使用。 先列一下使用到的主要工具&am…

计算机毕业设计】springbootBBS论坛系统

本系统为用户而设计制作 BBS论坛系统&#xff0c;旨在实现BBS论坛智能化、现代化管理。本BBS论坛自动化系统的开发和研制的最终目的是将BBS论坛的运作模式从手工记录数据转变为网络信息查询管理&#xff0c;从而为现代管理人员的使用提供更多的便利和条件。使BBS论坛系统数字化…

SpringCloud使用Nacos作为配置中心实现动态数据源切换

一、Nacos-Server 了解Nacos可以直接阅读官方文档 使用Nacos&#xff0c;我们需要有Nacos-Server&#xff0c;此处就不使用官方提供的release版本了&#xff0c;而是自己编译&#xff0c;因为本来就是Java开发的&#xff0c;所以对于Javaer来说也没啥难度&#xff01; git c…

解决NVM 下载node.js慢问题->最新镜像

一、NVM 介绍 nvm是node版本管理工具&#xff0c;可以运行在多种操作系统上。这里主要记录一下在windows系统的安装和使用。 在使用过程中&#xff0c;下载其他版本时会出现下载慢或卡住或下载失败的情况&#xff0c;是因为服务器在国外&#xff0c;网络原因导致&#xff0c;…

【神经网络】输出层的设计

文章目录 前言一、恒等函数和softmax函数恒等函数softmax 函数python实现softmax函数 二、实现softmax函数时的注意事项函数优化python实现 三、softmax函数的特征计算神经网络的输出输出层的softmax函数可以省略“学习”和“推理”阶段 四、输出层的神经元数量 前言 神经网络…