为了继续改进 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