单元测试mock框架Mockito

为了继续改进 Mockito 并进一步改善单元测试体验,我们希望您升级到 2.1.0!Mockito 遵循语义版本控制,仅在主要版本升级时包含重大更改。在库的生命周期中,重大更改是推出一组全新功能所必需的,这些功能会改变现有行为甚至更改 API。有关新版本(包括不兼容更改)的综合指南,请参阅“ Mockito 2 中的新功能”维基页面。我们希望您喜欢 Mockito 2!
0.1. Mockito Android 支持 链接图标
使用 Mockito 2.6.1 版,我们提供了“原生”Android 支持。要启用 Android 支持,请将“mockito-android”库作为依赖项添加到您的项目中。此工件已发布到同一个 Mockito 组织,可以按如下方式导入 Android:

 repositories {
   
   
   mavenCentral()
 }
 dependencies {
   
   
   testCompile "org.mockito:mockito-core:+"
   androidTestCompile "org.mockito:mockito-android:+"
 }

您可以在“testCompile”范围内使用“mockito-core”工件继续在常规 VM 上运行相同的单元测试,如上所示。请注意,由于 Android VM 的限制,您无法在 Android 上使用内联模拟生成器。如果您在 Android 上遇到模拟问题,请在官方问题跟踪器上打开问题 。请提供您正在使用的 Android 版本和项目的依赖项。
0.2.无需配置的内联模拟制作 链接图标
从 2.7.6 版开始,我们提供了“mockito-inline”工件,无需配置 MockMaker 扩展文件即可实现内联模拟制作。要使用此功能,请添加“mockito-inline”工件而不是“mockito-core”工件,如下所示:

 repositories {
   
   
   mavenCentral()
 }
 dependencies {
   
   
   testCompile "org.mockito:mockito-inline:+"
 }

请注意,从 5.0.0 开始,内联模拟制作器成为默认模拟制作器,并且该工件可能会在未来版本中被废除。
有关内联模拟制作的更多信息,请参阅第 39 节。

0.3.明确设置用于内联模拟的检测 (Java 21+) 链接图标
从 Java 21 开始,JDK 限制了库将 Java 代理附加到其自己的 JVM 的能力。 因此,如果没有明确设置启用检测,inline-mock-maker 可能无法运行,并且 JVM 将始终显示警告。
要在测试执行期间明确附加 Mockito,需要将库的 jar 文件指定为-javaagent 执行 JVM 的参数。要在 Gradle 中启用此功能,以下示例将 Mockito 添加到所有测试任务中:

 val mockitoAgent = configurations.create("mockitoAgent")
 dependencies {
   
   
     testImplementation(libs.mockito)
     mockitoAgent(libs.mockito) {
   
    isTransitive = false }
 }
 tasks {
   
   
     test {
   
   
         jvmArgs("-javaagent:${mockitoAgent.asPath}")
     }
 }

假设Mockito在版本目录中声明如下

 [versions]
 mockito = "5.14.0"

 [libraries]
 mockito = {
   
    module = "org.mockito:mockito-core", version.ref = "mockito" }

要将 Mockito 作为代理添加到 Maven 的 surefire 插件中,需要进行以下配置:

 <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-dependency-plugin</artifactId>
     <executions>
         <execution>
             <goals>
                 <goal>properties</goal>
             </goals>
         </execution>
     </executions>
 </plugin>
 <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-surefire-plugin</artifactId>
     <configuration>
         <argLine>@{
   
   argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
     </configuration>
 </plugin>

1.让我们验证一些行为
以下示例模拟了一个 List,因为大多数人都熟悉该接口(例如 add()、get()、clear()方法)。
实际上,请不要模拟 List 类。请改用真实实例。

 //Let's import Mockito statically so that the code looks clearer
 import static org.mockito.Mockito.*;

 //mock creation
 List mockedList = mock(List.class);

 //using mock object
 mockedList.add("one");
 mockedList.clear();

 //verification
 verify(mockedList).add("one");
 verify(mockedList).clear();

一旦创建,模拟就会记住所有交互。然后,您可以选择性地验证您感兴趣的任何交互。

2.来点存值怎么样

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

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

 //following prints "first"
 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);

默认情况下,对于所有返回值的方法,模拟将根据情况返回 null、原始/原始包装器值或空集合。例如,对于 int/Integer 返回 0,对于 boolean/Boolean 返回 false。
存根可以被覆盖:例如,常见的存根可以转到夹具设置,但测试方法可以覆盖它。请注意,覆盖存根是一种潜在的代码异味,指出了过多的存根
一旦被存根,该方法将始终返回一个存根值,无论它被调用多少次。
最后的存根更为重要 - 当您多次使用相同的参数存根相同的方法时。换句话说:存根的顺序很重要,但它很少有意义,例如当存根完全相同的方法调用或有时使用参数匹配器时,等等。
3.参数匹配器
Mockito 以自然的 Java 风格验证参数值:通过使用equals()方法。有时,当需要额外的灵活性时,您可能会使用参数匹配器:

 //stubbing using built-in anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");

 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 when(mockedList.contains(argThat(isValid()))).thenReturn(true);

 //following prints "element"
 System.out.println(mockedList.get(999));

 //you can also verify using an argument matcher
 verify(mockedList).get(anyInt());

 //argument matchers can also be written as Java 8 Lambdas
 verify(mockedList).add(argThat(someString -> someString.length() > 5));

参数匹配器允许灵活的验证或存根。 查看更多内置匹配器和自定义参数匹配器/ hamcrest 匹配器的示例。 Click here or here

有关自定义参数匹配器的信息请查看ArgumentMatcher类的 javadoc。

合理使用复杂的参数匹配。equals()偶尔使用anyX()匹配器的自然匹配风格往往会产生干净而简单的测试。有时最好重构代码以允许equals()匹配,甚至实现equals()方法来帮助测试。

另外,请阅读第 15 节或ArgumentCaptor类的 javadoc。 ArgumentCaptor是参数匹配器的特殊实现,用于捕获参数值以进行进一步的断言。

参数匹配器警告:

如果您使用参数匹配器,则所有参数都必须由匹配器提供。

下面的示例显示了验证,但同样适用于存根:

   verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
   //above is correct - eq() is also an argument matcher

   verify(mock).someMethod(anyInt(), anyString(), "third argument");
   //above is incorrect - exception will be thrown because third argument is given without an argument matcher.

匹配器方法(例如any())eq() 不返回匹配器。在内部,它们在堆栈上记录匹配器并返回虚拟值(通常为 null)。此实现是由于 Java 编译器强加的静态类型安全性。结果是您不能在已验证/存根方法之外 使用any()、方法。eq()

4.验证确切的调用次数/ 至少 x / 从不

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

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

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

 //following two verifications work exactly the same - times(1) is used by default
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 //exact number of invocations verification
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科学熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值