Java 单元测试(3)mock进阶 - 静态、final、私有方法mock

2023-11-09

前言

上一章讲了Spring-boot的starter test使用mock的方式mockito。但是mockito由于实现方式的原因(动态代理)不能支持静态、final、私有方法的mock。其实还有一种叫native方法,只是一般自己写native方法的地方不多,可能Android系统在这方面使用较多,比如游戏。查询了一些资料与笔者的以往经历,主要使用的有powerMock与jMockit。

1. powerMock

1.1. powerMock官方文档

powerMock在以前使用较多,最近反而使用少了,根本原因是不支持Junit5。官方最新版只支持Junit4,见官方文档

描述的很清楚,支持junit4版本,或者testNG,笔者在maven仓库看到junit5的支持jar,但是没人使用,更恶心的是有个版本号居然不能显示,看起来不是官方推送的。

所以笔者使用testNG来测试

1.2. powerMock demo模拟

pom文件如下,笔者依赖testNG与AssertJ。

		<dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.1.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-testng</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.3.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>

随意写一个方法,包含静态、私有、final方法。如果是final类,方式同final方法。

package com.feng.demo;

public class User {

    private String name;

    public String getResult(String test){
        return test + "\tjunit";
    }

    private String getPrivateName(String test){
        return "123";
    }

    public final String getFinalName(String str) {
        return "1235" + str;
    }

    public static String getStaticName(String string) {
        return "12356" + string;
    }

}

构建test方法,testNG需要继承PowerMockTestCase。

package com.feng.demo;


import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.powermock.reflect.Whitebox;
import org.testng.annotations.Test;

import java.lang.reflect.Method;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;

@PrepareForTest(User.class)//final方法准备,final类同理
public class UserTest extends PowerMockTestCase {

    @Mock
    private User user;

    @Test
    public void testTestGetResult() {
    	//由于次案例使用mockito,为powermock扩展,普通mock等同于mockito
        PowerMockito.when(user.getResult(anyString())).thenReturn("powerMock");
        String result = user.getResult("tom");
        assertThat(result).isEqualTo("powerMock");
    }

    @Test
    public void testGetFinalName() {
    	//final方法mock;这里同时mock了参数,跟final无关
        PowerMockito.when(user.getFinalName(argThat(s -> true))).thenReturn("powerMock");
        String result = user.getFinalName("tom");
        assertThat(result).isEqualTo("powerMock");
    }

    @Test
    public void testGetStaticName() {
    	//静态方法mock,先要设置需要mock的静态类
        PowerMockito.mockStatic(User.class);
        PowerMockito.when(User.getStaticName(argThat(s -> true))).thenReturn("powerMock");
        String result = User.getStaticName("tom");
        assertThat(result).isEqualTo("powerMock");
    }

    @Test
    public void testGetPrivateName() throws Exception {
    	//mock私有方法
        PowerMockito.when(user, "getPrivateName", anyString()).thenReturn("powerMock");
        //私有方法实现单元测试,本质是反射调用
        Method method = PowerMockito.method(User.class, "getPrivateName", String.class);
        Object result = method.invoke(user, "12");
        assertThat(result).isEqualTo("powerMock");

        Object say = Whitebox.invokeMethod(user, "getPrivateName", "12");
        assertThat(result).isEqualTo("powerMock");
    }
}

2. JMockit

重点介绍jmockit,功能十分强悍,只是测试代码有点不美观。不知道Spring-Boot推荐mockito而不是jmockit的原因是否是这个。jmockit查资料说支持mock私有方法,但笔者通过1.49版本测试是不支持的。

jmockit的本质Record-Replay-Verification

  1. Record: 录制类/对象的方法调用,在mockito里即为打桩。
  2. Replay: 重放方法,即调用方法。
  3. Verification: 验证。比如验证某个方法有没有被调用,调用多少次。

jmockit更新不是很频繁,最近更新是2019-12的1.49版本,支持junit5,这是jmockit1版本,一直在更新。

而且作者有jmockit2项目不知为啥2017年后不维护了。

2.1. jmockit demo

pom依赖

		<!-- Jmockit -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.49</version>
            <scope>test</scope>
        </dependency>

        <!-- junit5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>

继续使用上文的User作为需要测试的类。
这里要注意,仅仅依赖jar在maven的test是不管用的,笔者调试发现JMockit在执行单元测试时没有初始化,甚至笔者@ExtendWith(JMockitExtension.class)注入类都直接报错了。
查询官方文档:jmockit doc

笔者加入插件立马正常了,笔者查询很多博客,都没有这个,但根据笔者实践与官方文档是需要的。

<build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <argLine>
                        -javaagent:"${settings.localRepository}"/org/jmockit/jmockit/1.49/jmockit-1.49.jar
                    </argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>

而且官方还给出了单元测试代码覆盖率输出的配置:Activating coverage in a Maven project
其实现在而言用处不大,一般使用jacoco也可以在sonarqube配置。

2.2. @Mocked

@Mocked修饰类或者接口,类或者接口被修饰后,是全局的,以后这个类或者对象的方法调用就会走mocked的实例,不会再执行原来的方法。@Mocked非常霸道,自己new一个对象也不会生效了,全部被Mocked的实例接管。
返回结果jmockit也会处理:

  1. 原始类型(short,int,float,double,long)返回0
  2. String返回null
  3. 其它引用类型,返回这个引用类型的Mocked对象,即将返回的其他对象也Mocked了。

测试一下,jmockit有两种注入方式

模拟单元测试验证正确,

Mocked注解要慎用,一旦Mocked,所有对象全部被Mocked,建议在方法的参数上使用注解,就像笔者上面的示例一样,这样只会接管当前方法有效,同理因为这个原因,使用Mocked可以支持静态方法mock。

2.3. @Injectable

Mocked注解会对所有范围内的类或者实例全部Mocked,如果只想Mocked当前实例,就需要@Injectable注解,由于仅仅Mocked当前实例,所有静态方法失效,new的对象失效。

	@Test
    void mocked(@Injectable User user){
        assertThat(user.getFinalName("sss")).isNull();
        assertThat(user.getResult("sss")).isNull();
        assertThat(User.getStaticName("sss")).isNotNull();
        assertThat(new User().getFinalName("sss")).isNotNull();
    }

2.4. @Test

表示被测试的对象,如果我们没有实例化,JMockit会帮我们实例化。如果带有Injectable注解的属性,JMockit会使用Injectable构造函数实例化,如果没有这种构造函数,这会通过默认构造函数实例化,通过属性注入Injectable注解的属性。etg

public class SendMailService {
    
    public String sendmail(){
        return "OK";
    }
}

@Test类

public class User {

    private SendMailService sendMailService;

    public String send(String str){
        return str + sendMailService.sendmail();
    }
}

开始测试

2.5. @Capturing

@Capturing同样是mock对象,但与@Mocked或者@Injectable是有很大区别的。
@Capturing可以mock接口或者实现类的子类的行为。平时基本上不用,但是在AOP切面的过程可以直接mock接口或者类的子类行为。也就是说除了有@Mocked的能力,还会对动态代理或者自己写的子类mock。etg

public interface AopService {
    String doAop(String param);
}

假设这是一个动态代理的接口:实现类N多,做了一个切面;或者这个接口没有实现,类似mybatis的mapper。这个时候@Capturing就派上用场了。可以模拟子类或者实现类的行为。

class AopServiceTest {

    @Capturing
    AopService aopService;

    @Test
    void doAop() {
        new Expectations(){{
            aopService.doAop(anyString);
            result = "aopMock";
        }};

        assertThat(aopService.doAop("sss")).isEqualTo("aopMock");
    }
}

2.6. Expectations录制,功能类似mockito的打桩

expectations的录制其实有2种方式,可录制静态方法,final方法,但私有方法与native方法不可录制。

  1. @Injectabe,@Mocked,@Capturing配套使用,主要方式,基本都使用这种方式。
  2. 通过构造函数录制

    对象录入正常;但是当笔者将User.class传入构造函数都报错了,推荐使用MockUp替代。

    当笔者使用ArrayList的时候居然不行,估计是需要配合

    笔者查看源码发现,部分mocking并不是所有类或者对象都可以。原来有特殊验证

2.7. MockUp & @Mock

Invalid Class argument for partial mocking (use a MockUp instead)
笔者上一章报了这个错,推荐我们使用MockUp,静态方法,native,final方法,但私有方法不可录制,而且要写很多代码。
测试私有方法mock时,难道1.49版本不允许mock私有方法了?
java.lang.IllegalArgumentException: Unsupported fake for private method
看见有人回复想办法mock私有方法,源码分析是被作者禁了,至于是否有新的方式实现也没看见说明,官方API文档也没发现
在这里插入图片描述

非private方法是可以正常MockUp的。

	@Test
    void doAopMockUp() {
        User user = new User();
        new MockUp<User>(User.class){
            @Mock
            public String getResult(String test){
                return "mock";
            }
        };
        assertThat(user.getResult("111")).isEqualTo("mock");
    }

这种方式很灵活,可以实现自定义部分mock,估计对公共类使用是很好的场景。
但是缺点很明显:

  1. 代码写的很多
  2. 接口有多个实现,只能mock一个实现
  3. 动态实现无法mock,比如mybatis的mapper

2.8. Verifications

Verifications用于验证录制是否执行了,对比上一章的mockito,发现mock的3要素都是不变的。mockito是打桩-执行-验证,jmockit是录制-回放-验证。设计原理差不多,Verifications方面感觉jmockit就比较弱了。

	@Test
    void doAopMockUp() {
        User user = new User();
        new MockUp<User>(User.class){
            @Mock
            public String getResult(String test){
                return "mock";
            }
        };
        assertThat(user.getResult("111")).isEqualTo("mock");

        new Verifications(){
            {
                user.getResult("111");
                times = 1;
            }
        };

2.9. Spring 集成

以Spring Boot为例,pom依赖如下

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.2.4.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.49</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <argLine>
                        -javaagent:"${settings.localRepository}"/org/jmockit/jmockit/1.49/jmockit-1.49.jar
                    </argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>

可以使用@Tested与@Injectable配合Spring的注解同时使用,使用上一章的示例

@SpringBootTest
class DemoServiceImplTest {

    @Tested
    @Autowired
    private DemoService demoService;//这里就可以使用接口类型了,这点jmockit强大

    @Test
    void getMapperData(@Injectable DemoMapper demoMapper) {
        new Expectations(){
            {
                demoMapper.getName(anyString);
                result = "JMockit";
            }
        };

        assertThat(demoService.getMapperData("sss")).isNotBlank().isEqualTo("JMockit");
    }
}

在这里插入图片描述

总结

从功能上讲jmockit是很强大的,但是笔者测试1.49版本无法mock私有方法,powerMock却是可以,但是powerMock不支持junit5。估计Spring Boot官方集成mockito而不是jmockit主要是:

  1. mock静态,final,私有方法不是特别需求
  2. 代码风格,jmockit需要写大量的内部类,内部类代码块
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 单元测试(3)mock进阶 - 静态、final、私有方法mock 的相关文章

  • Hibernate注解放置问题

    我有一个我认为很简单的问题 我见过两种方式的例子 问题是 为什么我不能将注释放在字段上 让我举一个例子 Entity Table name widget public class Widget private Integer id Id G
  • Java:迭代 Collection 的最佳方法(此处为 ArrayList)

    今天 当我看到一段我已经使用了数百次的代码时 我很高兴地开始编码 迭代集合 此处为 ArrayList 出于某种原因 我实际上查看了 Eclipse 的自动完成选项 这让我想知道 在什么情况下以下循环比其他循环更好使用 经典的数组索引循环
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • java中如何连接字符串

    这是我的字符串连接代码 StringSecret java public class StringSecret public static void main String args String s new String abc s co
  • 运行具有外部依赖项的 Scala 脚本

    我在 Users joe scala lib 下有以下 jar commons codec 1 4 jar httpclient 4 1 1 jar httpcore 4 1 jar commons logging 1 1 1 jar ht
  • 如何模拟从抽象类继承的受保护子类方法?

    如何使用 Mockito 或 PowerMock 模拟由子类实现但从抽象超类继承的受保护方法 换句话说 我想在模拟 doSomethingElse 的同时测试 doSomething 方法 抽象超类 public abstract clas
  • 在 S3 中迭代对象时出现“ConnectionPoolTimeoutException”

    我已经使用 aws java API 一段时间了 没有遇到太多问题 目前我使用的是库 1 5 2 版本 当我使用以下代码迭代文件夹内的对象时 AmazonS3 s3 new AmazonS3Client new PropertiesCred
  • Hazelcast 分布式锁与 iMap

    我们目前使用 Hazelcast 3 1 5 我有一个简单的分布式锁定机制 应该可以跨多个 JVM 节点提供线程安全性 代码非常简单 private static HazelcastInstance hInst getHazelcastIn
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • 以编程方式在java的resources/source文件夹中创建文件?

    我有两个资源文件夹 src 这是我的 java 文件 资源 这是我的资源文件 图像 properties 组织在文件夹 包 中 有没有办法以编程方式在该资源文件夹中添加另一个 properties 文件 我尝试过这样的事情 public s
  • 有没有一种快速方法可以从 Jar/war 中删除文件,而无需提取 jar 并重新创建它?

    所以我需要从 jar war 文件中删除一个文件 我希望有类似 jar d myjar jar file I donot need txt 的内容 但现在我能看到从 Linux 命令行执行此操作的唯一方法 不使用 WinRAR Winzip
  • 在 Spring 中重构这个的最佳方法?

    private final ExecutorService executorParsers Executors newFixedThreadPool 10 public void parse List
  • 如何在JSTL中调​​用java方法? [复制]

    这个问题在这里已经有答案了 这可能是重复的问题 我只想调用不是 getter 或 setter 方法的方法例如 xyz 类的 makeCall someObj stringvalue Java类 Class XYZ public Strin
  • 测试弱引用

    在 Java 中测试弱引用的正确方法是什么 我最初的想法是执行以下操作 public class WeakReferenceTest public class Target private String value public Targe
  • HQL Hibernate 内连接

    我怎样才能在 Hibernate 中编写这个 SQL 查询 我想使用 Hibernate 来创建查询 而不是创建数据库 SELECT FROM Employee e INNER JOIN Team t ON e Id team t Id t
  • 将 Azure AD 高级自定义角色与 Spring Security 结合使用以进行基于角色的访问

    我创建了一个演示 Spring Boot 应用程序 我想在其中使用 AD 身份验证和授权 并使用 AD 和 Spring Security 查看 Azure 文档 我执行了以下操作 package com myapp contactdb c
  • javafx android 中的文本字段和组合框问题

    我在简单的 javafx android 应用程序中遇到问题 问题是我使用 gradle javafxmobile plugin 在 netbeans ide 中构建了非常简单的应用程序 其中包含一些文本字段和组合框 我在 android
  • 具有特定参数的 Spring AOP 切入点

    我需要创建一个我觉得很难描述的方面 所以让我指出一下想法 com x y 包 或任何子包 中的任何方法 一个方法参数是接口 javax portlet PortletRequest 的实现 该方法中可能有更多参数 它们可以是任何顺序 我需要
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • 如何从 Maven 存储库引用本机 DLL?

    如果 JAR 附带 Maven 存储库中的本机 DLL 我需要在 pom xml 中放入什么才能将该 DLL 放入打包中 更具体地举个例子Jacob http search maven org artifactdetails 7Cnet s

随机推荐

  • Java开发必学!kafka从入门到精通

    四面阿里 面试岗位是研发工程师 直接找蚂蚁金服的大佬进行内推 参与了阿里巴巴中间件部门的提前批面试 一共经历了四次面试 拿到了口头offer 一面 自我介绍 项目中做了什么 难点呢 Java的线程池说一下 各个参数的作用 如何进行的 Red
  • Could NOT find CUDNN: Found unsuitable version “..“, but required is at least “6“

    现象 在编译cuda版本的opencv4 1时 使用cmake配置时找不到cudnn 实际已安装 原因 由于安装的cudnn版本为8 2 原先记录cudnn版本的宏定义 CUDNN MAJOR和CUDNN MINOR 由cudnn h 移动
  • python--数据分析pandas

    1 pandas概述 Pandas是进行科学数据分析中另一个比较常用的数据库 基于NumPy 但加入了更多的高级数据结构以及操作工具 进一步简化了NumPy等运算与应用 1 1安装 pip install pandas 可以通过import
  • mysql主备 jdbc链接_mysql8 参考手册-使用JDBC配置连接的服务器故障转移

    MySQL Connecto J支持服务器故障转移 当基础活动连接发生与连接有关的错误时 将发生故障转移 连接错误 默认情况下 传递给客户端 其中有通过处理它们 例如 重新创建工作对象 Statement ResultSet 等 并重新启动
  • 线程结束的三种方式

    停止一个线程通常意味着在线程处理任务完成之前停掉正在做的操作 也就是放弃当前的操作 在 Java 中有以下 3 种方法可以终止正在运行的线程 使用退出标志 使线程正常退出 也就是当 run 方法完成后线程中止 使用 stop 方法强行终止线
  • QT之paintEvent事件

    当发生一下情况时会产生绘制事件并调用paintEvent 函数 1 在窗口部件第一次显示时 系统会自动产生一个绘图事件 从而强制绘制这个窗口部件 2 当重新调整窗口部件的大小时 系统也会产生一个绘制事件 3 当窗口部件被其他窗口部件遮挡 然
  • 解决小程序无法正常显示.webp格式图片的问题

    在小程序开发过程中可能会出现 微信开发者可以正常显示图片 而用iPhone预览或真机调试的时候却没有显示图片 的问题 有可能是因为图片格式iOS不支持所导致 将其图片格式转为 jpg格式即可 解决方法 判断系统是否为iOS 是 可用正则替换
  • 设备管理器没有生物识别解决方法

    Windows11的设备管理器中未显示生物识别设备 Win 电脑技术网 it资讯 游戏攻略 手机教程 电脑教程 无线路由器设置 设置无线路由器 办公软件教程 电脑系统安装 电脑维修知识 tagxp com
  • Flutter如何做到网络请求(多个网络请求)完成之后再加载页面

    很多时候我们有这样一个需求 需要在网络请求完了之后再去渲染页面 尤其是在一个界面有多个相关网络请求的时候需要处理 这里不得不提到一个系统的组件FutureBuilder 我们直接来看用法 FutureBuilder用法 单网络请求 避免重复
  • Jquery实现网页跳转或用命令打开指定网页!

    Jquery实现网页跳转或用命令打开指定网页 location href www baidu com location href aa aspx 在新窗口中打开链接 window open http www baidu com
  • oracle查看表空间及大小

    1 查看表空间的名称及大小 SELECT t tablespace name round SUM bytes 1024 1024 0 ts size FROM dba tablespaces t dba data files d WHERE
  • go对Json的处理

    目前再前后端传递数据的方式最广泛的引用使用到的就是Json的数据格式 go语言对Json也有一个非常良好的支持 Marshal 生成Json package main import encoding json fmt type Order
  • 计蒜客T1437——最大值和次大值

    比较简单的题 用STL库可以大幅度降低代码复杂度 将int型的数字压入到vector中 调用sort实现从小到大排序 再采用reverse将其翻转为从大到小 将第一个元素 最大值 首先输出 再遍历后面第一个与最大值不同的元素 其即为题干要求
  • Jmeter性能测试

    一 介绍 JMeter是一款测试工具 主要用于服务端的性能测试 如web网站 api服务器等 可以方便的获取来自不同压力下的性能指标 另外 JMeter能够对应用程序做功能 回归测试 通过创建带有断言的脚本来验证返回结果是否符合期望 二 安
  • 海思3861L搭建Linux开发环境基于ubuntu16.04

    1 拷贝工具链hcc riscv32 tar gz压缩包到ubuntu系统解压并新增环境变量 如下所示 vim etc profile export PATH toolchain hcc riscv32 bin PATH source et
  • 字符串分割QString::split

    同样是字符串分割 split 和section 相比不同之处在于前者将分割内容以list返回 split 有多种重载形式 QStringList QString split QChar sep QString SplitBehavior b
  • RocketMQ Pull和Push

    在rocketmq里 consumer被分为2类 MQPullConsumer和MQPushConsumer 其实本质都是拉模式 pull 即consumer轮询从broker拉取消息 区别是 push方式里 consumer把轮询过程封装
  • 【车道线检测】计算机视觉视频车道线检测 【含GUI Matlab源码 362期】

    一 Hough变换图片车道线检测简介 1 引言 随着人们生活水平的提高 科技的不断进步 智能驾驶技术逐渐受到了研究者们的广泛研究和关注 先进驾驶辅助系统 Advanced Driver Assistance System 简称ADAS 是智
  • 云函数请求第三方API

    云函数请求第三方API 构建环境 在云开发文件目录下通过npm 安装插件 request 和 request promise npm install save request npm install save request promise
  • Java 单元测试(3)mock进阶 - 静态、final、私有方法mock

    mock进阶 前言 1 powerMock 1 1 powerMock官方文档 1 2 powerMock demo模拟 2 JMockit 2 1 jmockit demo 2 2 Mocked 2 3 Injectable 2 4 Te