Java测试框架Mockito快速入门

Mockito结合TestNG快速入门

什么是Mockito

Mockito 是一个专门用于 Java 的强大测试框架,主要用来创建和管理模拟对象,辅助开发者进行单元测试,具有以下特点和功能:

  • 创建模拟对象:能通过简洁的语法创建类或接口的模拟版本,这些模拟对象可作为真实对象的替代品,在调试期间使用,帮助隔离外部依赖。比如在测试一个依赖其他服务(如账户服务、数据库访问服务等)的业务逻辑时,可模拟这些外部服务,避免在测试中涉及真实的复杂操作(如真的修改数据库数据、调用远程接口等) 。
  • 定义行为 :可以对模拟对象的方法进行 "桩(Stubbing)" 设置,即指定其返回值或让其抛出异常。例如,使用when(...).thenReturn(...)来定义方法返回特定值;用thenThrow()模拟方法抛出异常,方便测试错误处理逻辑。
  • 验证交互 :提供验证机制,通过verify(...)方法检查模拟对象的方法是否按照预期被调用,以及调用的次数,从而验证代码与模拟对象之间的交互是否正确

引入的依赖

mockito-core

复制代码
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.15.2</version>
    <scope>test</scope>
</dependency>

testng

复制代码
        <!-- https://mvnrepository.com/artifact/org.testng/testng -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.11.0</version>
            <scope>test</scope>
        </dependency>

为什么需要mock

可以知道这个方法的调用情况,调用了多少次参数就是多少

给这个对象的行为做一个定义,来指定返回结果或者指定特定的动作

当使用mock对象时,如果不对其行为进行定义,则mock对象方法的返回值为返回类型的默认值


验证和断言

验证:vertify()方法

验证是校验待验证的对象是否发生过某些行为

复制代码
package com.example.testng;

import org.mockito.Mockito;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {
    @Test
    public void test(){
        //mock一个对象
       Random mock= Mockito.mock(Random.class);
       //输出随机数
       System.out.println(mock.nextInt());
        //验证我们的mock对象是否用了nextInt()这个方法
        Mockito.verify(mock).nextInt();


    }
}

为什么我们的执行结果一直是0?

这是因为我们没有给我们的mock出来的对象定义行为

断言:Assert

判断我们返回的结果是否符合我们的预期

复制代码
package com.example.testng;

import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {
    @Test
    public void test() {
        //mock一个对象
        Random mock = Mockito.mock(Random.class);
        Mockito.when(mock.nextInt()).thenReturn(100);

        Assert.assertEquals(100, mock.nextInt());


    }
}

符合预期,我们成功通过

不符合预期,我们测试失败

复制代码
package com.example.testng;

import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {
    @Test
    public void test() {
        //mock一个对象
        Random mock = Mockito.mock(Random.class);
        Mockito.when(mock.nextInt()).thenReturn(100);

        Assert.assertEquals(101, mock.nextInt());


    }
}

给Mock对象进行打桩

打桩的意思其实就是对mock对象的行为进行定义

打桩的话是mockito里面的when()方法,定义我们的行为


@Mock注解

我们的mock注解必须搭配MockitoAnnotations.openMocks(testClass)方法一起使用

我们使用@Mock注解来Mock对象,而不是在代码里面手动Mock我们的对象

复制代码
package com.example.testng;

import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {


    @Mock
    private Random mock;
    @Test
    public void test() {
        MockitoAnnotations.openMocks(this);

        Mockito.when(mock.nextInt()).thenReturn(100);

        Assert.assertEquals(100, mock.nextInt());


    }
}

@BeforeTets与@AfterTest

复制代码
package com.example.testng;


import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {


    @Mock
    private Random mock;

    @BeforeTest
    void setup() {
        System.out.println("测试前的准备");
    }

    @AfterTest
    void end() {
        System.out.println("测试结束");
    }

    @Test
    public void test() {
        MockitoAnnotations.openMocks(this);

        Mockito.when(mock.nextInt()).thenReturn(100);

        Assert.assertEquals(100, mock.nextInt());


    }
}

Spy方法与@Spy注解

spy方法与mock方法的不同是

1.被spy的对象会走真实的方法而mock对象不会

2.spy{}方法的参数是对象实例,mock的参数是class

复制代码
package com.example.testng;


import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.util.Random;

public class MockitoDemoTest {


    @Spy
    private Random mock;

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


    @Test
    public void test() {


        Assert.assertEquals(100, mock.nextInt());


    }
}

我们没有打桩,而且我们是Spy注解,所以我们不会输出0,而是会去调用真正的Random方法

如果我们打桩了的话,那还是会按照我们打桩定义的规则来


打桩与Mock静态方法

4个常见的打桩方法

thenCallRealMethod

thenReturn

thenThrow

thenAnswer

复制代码
package com.example.testng;


import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.util.Random;

import static org.mockito.Mockito.when;

public class MockitoDemoTest {


    @Spy
    private Random mock;

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


    @Test
    public void test() {


        //调用真实方法
        when(mock.nextInt()).thenCallRealMethod();
        //定义返回值
        when(mock.nextInt()).thenReturn(1);
        //定义返回什么错误
        when(mock.nextInt()).thenThrow(new RuntimeException());
        //自定义响应逻辑
        when(mock.nextInt()).thenAnswer(new Answer<Integer>() {
            @Override
            public Integer answer(InvocationOnMock invocation) throws Throwable {
                // 这里可以编写自定义的逻辑
                // invocation 包含了方法调用的信息,例如方法名、参数等
                // 这里简单返回一个固定值 42
                return 42;
            }
        });

    }
}

Mock静态方法

依赖

mockito-inline依赖包含了mockito-code依赖

注意我们使用mockito-inline依赖的时候要把mockito-code依赖注释掉,我们不能同时引用

复制代码
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>

在软件开发的单元测试中,有时候我们需要对静态方法进行模拟(Mock),这样可以更好地控制测试环境、隔离依赖,提高测试的可维护性和稳定性。下面详细介绍模拟静态方法的相关内容,以 Java 语言结合 Mockito 框架为例


为什么需要 Mock 静态方法

隔离依赖:静态方法可能依赖于外部资源,如文件系统、数据库等。在单元测试中,我们希望避免这些外部依赖的影响,通过模拟静态方法可以将测试与外部资源隔离开来

控制返回值:静态方法的返回值可能受到多种因素的影响,通过模拟静态方法,我们可以精确控制其返回值,从而更方便地验证业务逻辑


使用例子

我们这个类里面有个静态方法

复制代码
public class StaticUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

使用mockStatic

使用 try-with-resources语句确保 MockedStatic 对象在使用完毕后自动关闭,避免对后续测试产生影响

复制代码
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mockStatic;

public class StaticUtilsTest {

    @Test
    public void testMockStaticMethod() {
        // 使用 try-with-resources 语句创建 MockedStatic 对象
        try (MockedStatic<StaticUtils> mockedStatic = mockStatic(StaticUtils.class)) {
            // 定义静态方法的行为
            mockedStatic.when(() -> StaticUtils.add(2, 3)).thenReturn(10);

            // 调用静态方法
            int result = StaticUtils.add(2, 3);

            // 验证结果
            assertEquals(10, result);
        }
    }
}

单元测试如何提高我们的代码覆盖率

@InjectMocks注解

剩下的@mock和@spy注解修饰的对象会自动注入到被InjectMocks注解修饰的对象里面

相关推荐
源码云商2 小时前
基于Spring Boot + Vue的母婴商城系统( 前后端分离)
java·spring boot·后端
冼紫菜5 小时前
【Spring Boot 多模块项目】@MapperScan失效、MapperScannerConfigurer 报错终极解决方案
java·开发语言·mybatis
还听珊瑚海吗5 小时前
基于SpringBoot的抽奖系统测试报告
java·spring boot·后端
练习本5 小时前
Android系统架构模式分析
android·java·架构·系统架构
心灵宝贝8 小时前
IDEA 安装 SpotBugs 插件超简单教程
java·macos·intellij-idea
幼稚诠释青春8 小时前
Java学习笔记(对象)
java·开发语言
小羊学伽瓦8 小时前
【Java基础】——JVM
java·jvm
老任与码8 小时前
Spring AI(2)—— 发送消息的API
java·人工智能·spring ai
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧9 小时前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
csdn_freak_dd9 小时前
查看单元测试覆盖率
java·单元测试