• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Mockito单元测试基本使用

武飞扬头像
蒋大钊!
帮助1

本文参考:

java - doReturn().when()与when().thenReturn() - 成长之路 - SegmentFault 思否

【Mockito】Mockito Junit 5 快速入门_哔哩哔哩_bilibili

1.为什么需要Mock

测试驱动的开发( TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。如下图所示:
学新通
为了测试类A,我们需要Mock B类和C类(用虚拟对象来代替)如下图所示:
学新通

2.Mockito 中常用方法

先添加maven依赖:

mockito和junit:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jxz</groupId>
    <artifactId>MockitoLearning</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>4.3.1</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    </dependencies>

</project>

2.1 Mock 方法

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

public static <T> T mock(Class<T> classToMock)
  • classToMock:待 mock 对象的 class 类。
  • 返回 mock 出来的类

实例:使用 mock 方法 mock 一个List类

List mockList = Mockito.mock(List.class);

也可以使用注解来快速模拟

  1. @Mock MockitoAnnotations.openMocks(this)

也有用@Mock MockitoAnnotations.initMocks(this)的

package com.jxz;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

import static org.mockito.Mockito.verify;
/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/19
 */
public class MockExample1 {
    @Mock
    private List mockList;

    @Before
    public void setup(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testMock(){
        mockList.add(1);
        verify(mockList).add(1);
    }
}
  1. @Mock @RunWith(MockitoJUnitRunner.class)
package com.jxz;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.List;

import static org.mockito.Mockito.verify;
/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/19
 */
@RunWith(MockitoJUnitRunner.class)
public class MockExample2 {
    @Mock
    private List mockList;

    @Test
    public void testMock(){
        mockList.add(1);
        verify(mockList).add(1);
    }
}

2.2 对 Mock 出来的对象进行行为验证和Junit结果断言

一旦mock对象被创建了,Mock对象会记录我们调用的所有交互,也就是各种方法和其调用参数,验证的意思是”查看我们到底有没有输入特定的调用参数,调用过mock的这个方法“,Mockito 中验证的方法是:verify

package com.jxz;

import org.junit.Test;
import org.mockito.Mockito;

import java.util.List;

import static org.mockito.Mockito.verify;
/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/19
 */
public class Demo {
    @Test
    public void testMock(){
        List mockList = Mockito.mock(List.class);
        mockList.add(1);
        mockList.add("one");

        // verify
        verify(mockList).add(1);
//        verify(mockList).add("two"); //会抛出异常
    }
}

JUnit断言使用到的类是 Assert.

@Test
public void testMock2(){
    List mockList = Mockito.mock(List.class);
    mockList.add(1);

    Assert.assertEquals(1,mockList.get(1));
}

输出:

java.lang.AssertionError: 
Expected :1
Actual   :null

当使用 mock 对象时(这里是mockList),如果不对其行为进行定义(也就是下面的打桩),则 mock 对象方法的返回值为返回类型的默认值(这里为null)。

2.3 测试桩stub

指定mock对象的交互行为逻辑,基本格式when().thenReturn()

when(mockedList.get(0)).thenReturn(“first”)规定当调用get(0)的时候返回"first"

@Test
public void testStub(){
    LinkedList mockList = Mockito.mock(LinkedList.class);
    // stubbing
    when(mockList.get(0)).thenReturn("first");
    when(mockList.get(1)).thenThrow(new RuntimeException());

    System.out.println(mockList.get(0)); //first
    System.out.println(mockList.get(1)); //RuntimeException
}

当我们连续两次为同一个方法使用stub的时候,他只会只用最新的一次。一旦这个方法被stub了,就会一直返回这个stub的值。如下:

@Test
public void testStub2(){
    LinkedList mockList = Mockito.mock(LinkedList.class);
    when(mockList.get(0)).thenReturn("first");
    when(mockList.get(0)).thenReturn("second");
    System.out.println(mockList.get(0));
    System.out.println(mockList.get(0));
}

输出:

second
second

还有一种测试桩指定的方式,即doReturn().when(),本质上也是规定行为。

@Test
public void testDoReturn(){
    A a = new A();
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.add(1,2)).thenReturn(5); // 当mockA调用add(1,2),返回5
    System.out.println(mockA.add(1,2)); // 5

    A A2 = new A();
    A mockA2 = Mockito.mock(A.class);
    Mockito.doReturn(5).when(mockA2).add(1,2); // 同样是当mockA调用add(1,2),返回5
    System.out.println(mockA2.add(1,2)); // 5
}

两者本质上就是一个由于执行顺序产生的问题,Mockito.when(mockA.add(1,2)).thenReturn(5),会先执行a b,即 1 2 , 结果本应是3,但由于后面的thenReturn,所以调用该方法时,实际的返回值是5。而Mockito.doReturn(5).when(mockA2).add(1,2)就不会执行a b的操作,而会直接返回5。区别就是若是一定会执行add()方法,难免会产生无法预估的副作用,比如抛出异常等。

2.4 参数匹配器

参数匹配器可以进行参数的灵活指派。

@Test
public void testMatch(){
    Map mockMap = Mockito.mock(Map.class);
    // 正常打桩测试
    when(mockMap.get("key1")).thenReturn("value1");
    System.out.println(mockMap.get("key1")); //value1

    // 任意String的参数匹配器
    when(mockMap.get(anyString())).thenReturn("value2");
    System.out.println(mockMap.get(anyString())); // value2
    System.out.println(mockMap.get("key2")); // value2
    System.out.println(mockMap.get(1)); // null

    // 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常, put()返回参数
    when(mockMap.put(anyString(),anyInt())).thenReturn("value3");
    System.out.println(mockMap.put("key3",3)); // value3
    System.out.println(mockMap.put(anyString(),anyInt())); // value3
//        System.out.println("key3",anyInt()); // 异常

    // verify也支持参数匹配
    verify(mockMap,atLeastOnce()).get(anyString()); // 前面交互至少调用了一次get(anyString())
    verify(mockMap).put(anyString(),eq(3)); // 前面至少调用了一次put(anyString(),3)
}

2.5 mock()与spy()

  1. 被 spy 的对象会走真实的方法,而 mock 对象走虚拟对象的方法,返回默认值
  2. spy() 方法的参数是对象实例,mock()方法 的参数是 class

示例:

package com.jxz;

import org.junit.Test;
import org.mockito.Mockito;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/20
 */
public class SpyAndMock {

    @Test
    public void testSpy(){
        A a = new A();
        A a1 = Mockito.mock(A.class);
        A a2 = Mockito.spy(a);
        System.out.println(a1.add(1,2));
        System.out.println(a2.add(1,2));
    }
}

class A{
    public int add(int a, int b){
        return a b;
    }
}

输出:

0
3

通过mock生成的对象,会拥有以前的对象的所有方法,但是方法中都没有了功能,就比如上面的a1对应的类可以理解下面这样

A1 extend A {  
    pubic int add(int a, int b) {  
        return 0;  
  }  
}

2.6 @InjectMocks

@InjectMocks和@Mock配合可以简化某个类中,注入类的配置。同时可以在外面对被@Mock的类进行行为指定,从而让其被调用时,产生我们指定的结果。

示例:

要测试的类和方法:

package com.jxz;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/20
 */
public class RegistrationImpl {
    SalesDao salesDao = new SalesDao();
    SaveDao saveDao = new SaveDao();
    public String register(int id, String name){
        String result1 = salesDao.findRep(name);
        System.out.println(result1);

        String result2 = saveDao.save(id, name);
        System.out.println(result2);

        return result1   "_"   result2;
    }
}

class SalesDao{
    public String findRep(String name){
        return name;
    }
}

class SaveDao{
    public String save(int id, String name){
        return id   name;
    }
}

对应的测试类和方法:

package com.jxz;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.when;
/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/6/20
 */
@RunWith(MockitoJUnitRunner.class)
public class RegistrationImplTest {
    @InjectMocks
    private RegistrationImpl registrationImpl; // 需要被注入mock对象的对象
    @Mock
    private SalesDao salesDao; // RegistrationImpl中注入的类
    @Mock
    private SaveDao saveDao;

    @Test
    public void testRegister(){
        // 进行mock注入类的行为指定
        // 可以看到register方法中调用mock对象打印出来的东西正确
        when(salesDao.findRep("jiangxuzhao")).thenReturn("jiangxuzhao666");  // jiangxuzhao666
        when(saveDao.save(123,"jiangxuzhao")).thenReturn("123jiangxuzhao666"); // 123jiangxuzhao666

        String result = registrationImpl.register(123, "jiangxuzhao");
        Assert.assertEquals(result,"jiangxuzhao666_123jiangxuzhao666"); // 结合mock对象的返回值正确
//        Assert.assertEquals(result,"null"); // org.junit.ComparisonFailure
    }
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgiheak
系列文章
更多 icon
同类精品
更多 icon
继续加载