首页 > 编程语言 >[Java SE/Junit] 基于Java的单元测试框架Mockito[转载]

[Java SE/Junit] 基于Java的单元测试框架Mockito[转载]

时间:2022-12-15 15:35:26浏览次数:69  
标签:Java get assertEquals Mockito 单元测试 Assert add mockedList mock

Mockito 是一个模拟测试框架,主要功能是在单元测试中模拟类/对象的行为。

1 为什么要使用Mockito?

Mock可以理解为创建一个虚假的对象,或者说模拟出一个对象.在测试环境中用来替换掉真实的对象,以达到我们可以

  • 验证该对象的某些方法的调用情况,调用了多少次,参数是多少.
  • 给这个对象的行为做一个定义,来指定返回结果或指定特定的动作.

2 Mockito数据隔离

根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。

比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。

3 Mock方法

mock(Class classToMock)

mock方法来自org.mockito.Mock,它标识可以mock一个对象或者是接口

public static <T> mock(Class<T> classToMock);
  • 入参:classToMock: 待mock对象的class类
  • 返回:mock出来的类的对象(此时对象内依旧无数据)
Random random = Mockito.mock(Random.class);

Stub 存根 : 为mock对象规定预期的目标执行结果

简述

存根的意思就是给mock对象规定一行的行为,使其按照我们的要求来执行具体的动作。

样例程序

demo1

 //You can mock concrete classes, not just interfaces
 LinkedList mockedList = mock(LinkedList.class);

 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(0)).thenReturn("two");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

 //following prints "two"
 System.out.println(mockedList.get(0));

 //following throws runtime exception
 System.out.println(mockedList.get(1));

 //following prints "null" because get(999) was not stubbed
 System.out.println(mockedList.get(999));

 //Although it is possible to verify a stubbed invocation, usually it's just redundant
 //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
 //If your code doesn't care what get(0) returns, then it should not be stubbed.
 verify(mockedList).get(0);

demo2

@Test
public void test(){
    // 创建Mock对象,参数可以是类或者接口
    List<String> list = mock(List.class);

    // 设置方法的预期返回值
    when(list.get(0)).thenReturn("zuozewei");
    when(list.get(1)).thenThrow(new RuntimeException("test exception"));

    String result = list.get(0);

    // 验证方法调用
    verify(list).get(0);

    //断言,list的第一个元素是否是 "zuozwei"
    Assert.assertEquals(result,"zuozewei");
}

常用方法

  • when(mockObject.actionMethod).thenReturn(String t) 设置方法的目标返回值
  • when(mockObject.actionMethod).thenThrow(Throwable... throwables) 让方法抛出异常
  • when(mockObject.actionMethod).thenAnswer(Answer answer) / then(Answer answer) 自定义方法处理逻辑
  • when(mockObject.actionMethod).thenCallRealMethod() 调用 spy 对象的真实方法

reset(mockObject) : 重置之前自定义的返回值和异常

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));
    }

    @Test
    public void test2() {
        ExampleService exampleService = spy(new ExampleService());
        // spy 对象方法调用会用真实方法,所以这里返回 3
        Assert.assertEquals(3, exampleService.add(1, 2));
        // 设置让 add(1,2) 返回 100
        when(exampleService.add(1, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));
        // 重置 spy 对象,add(1,2) 返回 3
        reset(exampleService);
        Assert.assertEquals(3, exampleService.add(1, 2));
    }
}

参数匹配:精确匹配与模糊匹配(anyXXX)

import org.junit.Assert;
import org.junit.Test;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {


    @Test
    public void test() {
        List mockList = mock(List.class);

        Assert.assertEquals(0, mockList.size());
        Assert.assertEquals(null, mockList.get(0));

        mockList.add("a");  // 调用 mock 对象的写方法,是没有效果的

        Assert.assertEquals(0, mockList.size());      // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals(null, mockList.get(0));   // 没有指定 get(0) 返回值,这里结果是默认值

        when(mockList.get(0)).thenReturn("a");          // 指定 get(0)时返回 a

        Assert.assertEquals(0, mockList.size());        // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals("a", mockList.get(0));      // 因为上面指定了 get(0) 返回 a,所以这里会返回 a

        Assert.assertEquals(null, mockList.get(1));     // 没有指定 get(1) 返回值,这里结果是默认值

        // 参数匹配:精确匹配
        when(mockStringList.get(0)).thenReturn("a");
        when(mockStringList.get(1)).thenReturn("b");

        Assert.assertEquals("a", mockStringList.get(0));
        Assert.assertEquals("b", mockStringList.get(1));

        // 参数匹配:模糊匹配
        when(mockStringList.get(anyInt())).thenReturn("a");  // 使用 Mockito.anyInt() 匹配所有的 int
        
        Assert.assertEquals("a", mockStringList.get(0)); 
        Assert.assertEquals("a", mockStringList.get(1));
    }
}

@Mock 注解

@mock快速创建mock的方法,使用
@mock注解需要搭配MockitoAnnotations.openMocks(testClass)方法一起使用.

   public class ArticleManagerTest {

       @Mock 
       private ArticleCalculator calculator;
       @Mock 
       private ArticleDatabase database;
       @Mock 
       private UserProvider userProvider;

       private ArticleManager manager;

    
       @org.junit.jupiter.api.Test
       void testSomethingInJunit5() {
           // 初始化mock对象
           MockitoAnnotations.openMocks(testClass);
           //Mockito.mock(class);
           Mockito.spy(class);
       }

       // 简化
       // @BeforeEach
       // void setUp() {
       //     MockitoAnnotations.openMocks(this);
       // }
   
   }

@Spy注解 与 Spy方法

spy 和 mock不同,不同点是:

  • spy 的参数是对象实例,mock 的参数是 class。
  • 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。

@Spy注解需要搭配MockitoAnnotations.openMocks(testClass)方法一起使用.

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

class ExampleService {
    int add(int a, int b) {
        return a+b;
    }
}

public class MockitoDemo {

    // 测试 spy
    @Test
    public void test_spy() {
        ExampleService spyExampleService = spy(new ExampleService());
        // 默认会走真实方法
        Assert.assertEquals(3, spyExampleService.add(1, 2));
        // 打桩后,不会走了
        when(spyExampleService.add(1, 2)).thenReturn(10);
        Assert.assertEquals(10, spyExampleService.add(1, 2));
        // 但是参数比匹配的调用,依然走真实方法
        Assert.assertEquals(3, spyExampleService.add(2, 1));
    }

    // 测试 mock
    @Test
    public void test_mock() {
        ExampleService mockExampleService = mock(ExampleService.class);
        // 默认返回结果是返回类型int的默认值
        Assert.assertEquals(0, mockExampleService.add(1, 2));
    }
}

spy 对应注解 @Spy,和 @Mock 是一样用的。

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import static org.mockito.Mockito.*;

class ExampleService {
    int add(int a, int b) {
        return a+b;
    }
}

public class MockitoDemo {
    @Spy
    private ExampleService spyExampleService;

    @Test
    public void test_spy() {
        MockitoAnnotations.openMocks(this);
        Assert.assertEquals(3, spyExampleService.add(1, 2));
        when(spyExampleService.add(1, 2)).thenReturn(10);
        Assert.assertEquals(10, spyExampleService.add(1, 2));
    }
}

@InjectMocks : 注解注入 mock 对象

mockito 会将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。
注入方式有多种,mockito 会按照下面的顺序尝试注入:

  • 构造函数注入
  • 设值函数注入(set函数)
  • 属性注入
package demo;
import java.util.Random;

public class HttpService {
    public int queryStatus() {
        // 发起网络请求,提取返回结果
        // 这里用随机数模拟结果
        return new Random().nextInt(2);
    }
}
package demo;

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 "未知状态";
        }
    }
}
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.when;


public class ExampleServiceTest {

    @InjectMocks // 将httpService主动注入
    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());
    }
}

验证和断言: verify(...)方法

验证是校验待验证的对象是否发生过某些行为,Mockito中验证的方法是:verify

verify(mock).someMoethod("some arg");
verify(mock,times(100)).someMoethod("some arg");

使用verify验证(Junit的断言机制):

  @Test
  void check() {
    Random random = Mockito.mock(Random.class);
    System.out.println(random.nextInt());
    Mockito.verify(random,Mockito.times(2)).nextInt();
  }

Verify配合times()方法,可以校验某些操作发生的次数

 //using mock
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 // 默认调用1次
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 // 自定义调用多次
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 // 从不调用
 verify(mockedList, never()).add("never happened");

 // atLeast()/atMost()  至少调用 / 之多调用
 verify(mockedList, atMostOnce()).add("once");
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

 // 超时验证
 verify(mock, timeout(100)).someMethod();
 verify(mock, timeout(100).times(1)).someMethod();

 //只要 someMethod() 在 100 毫秒内被调用 2 次,就会通过
 verify(mock, timeout(100).times(2)).someMethod();

 //someMethod() 至少在 100 毫秒内被调用 2 次,就会通过
 verify(mock, timeout(100).atLeast(2)).someMethod();

X 参考文献

标签:Java,get,assertEquals,Mockito,单元测试,Assert,add,mockedList,mock
From: https://www.cnblogs.com/johnnyzen/p/16985122.html

相关文章

  • [Java SE/JDK]Intellij IDEA中设置JDK版本
    1IntellijIDEA修改JDK版本第1步:配置JDK环境变量装好JDK之后,要添加一个环境变量:JAVA_HOME第2步:修改Idea配置由Maven决定的版本<build><plugins><p......
  • Javascript-奖品概率算法
    constLUCKY_AIRDROP_PRIZE=[{"id":1,"prop":16.2},{"id":2,"prop":16.2},{"id":3,"prop":16.2},{"id":4,"prop":16.2},......
  • Java Socket网络编程
    1.TCP流式SocketTCP是TCP/IP体系结构中位于传输层的面向连接的协议,提供可靠的字节流传输。通信双方需要建立连接,发送端的所有数据按顺序发送,接受端按顺序接收。......
  • java泛型
    1.泛型方法/**<T>表示本方法持有一个不确定类型T,表示本方法是一个泛型方法*T返回值类型为T类型的对象*Class<T>clazz:只有通过反射才能拿到泛型T的对象,因为方......
  • java 浮点数 判断相等
    浮点数之间的等值判断,基本数据类型不能使用==进行比较,包装数据类型不能使用equals进行判断。说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指......
  • JVM的内存区域划分(面试问题:你了解java内存模型么)
    学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数据区。那么在Java语言当中,内存又是如何划分的呢?......
  • Java 如何有效地避免OOM:善于利用软引用和弱引用
    想必很多朋友对OOM(OutOfMemory)这个错误不会陌生,而当遇到这种错误如何有效地解决这个问题呢?今天我们就来说一下如何利用软引用和弱引用来有效地解决程序中出现的OOM问题......
  • java虚拟机指南
    JVM内存区域我们在编写程序时,经常会遇到OOM(outofMemory)以及内存泄漏等问题。为了避免出现这些问题,我们首先必须对JVM的内存划分有个具体的认识。JVM将内存主要划分为:方法......
  • Java技术点
    1.面经1.1.Redis1.缓存穿透,缓存击穿,缓存雪崩1.缓存穿透缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会被打倒数据库上。......
  • java内存模型
    一.内存模型的相关概念。大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在......