先明确一个词:存根(或者说是打桩),指的是对某个方法指定返回策略的操作(具体表现为两种:1指定返回值,2使用doCallRealMethod()或者thenCallRealMethod()指定当方法被调用时执行实际代码逻辑),功能就是当测试执行到此方法时直接返回我们指定的返回值(此时不会执行此方法的实际代码逻辑)或者执行此方法的实际代码逻辑并返回。比如:
我们先定义一个对象:
public class Mock {public String m1() {throw new RuntimeException();}
}
然后我们进行下面的操作:
Mock m = Mockito.mock(Mock.class);//对m1()方法进行存根
Mockito.doReturn("1").when(m).m1();
基于上面的代码我们可以说:我们对m对象的m1()方法进行了存根。
此时我们调用m对象的m1()方法时,可以直接得到返回值"1"而不会执行m1方法的实际代码逻辑:
Assert.assertEquals("1",m.m1());
此时断言为真。
Mockito.when(foo.sum()).thenXXX(...);
Mockito.doXXX(...).when(foo).sum();
Mockito.doXXX(....).when(foo.sum());
你会得到一个异常,即不应该使用这种方式!
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at c.FooTest.verifyTest(FooTest.java:23)E.g. thenReturn() may be missing.
Examples of correct stubbing:when(mock.isOk()).thenReturn(true);when(mock.isOk()).thenThrow(exception);doThrow(exception).when(mock).someVoidMethod();
Hints:1. missing thenReturn()2. you are trying to stub a final method, which is not supported3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
then_xxx方法 | do_XXX方法 | 功能 |
---|---|---|
then(Answer> answer) | doAnswer(Answer answer) | 返回值使用自定义的Answer策略。 |
thenAnswer(Answer> answer) | 同上 | 同上。 |
thenReturn(T value) | doReturn(Object toBeReturned) | 直接指定返回值。 |
thenReturn(T value, T... values) | doReturn(Object toBeReturned, Object... toBeReturnedNext) | 直接指定返回值,可以定义多个返回值。第一次调用到存根方法返回第一个返回值。以此类推。超过返回值数量的调用返回参数的最后一个返回值。 |
thenCallRealMethod() | doCallRealMethod() | 调用实际的代码逻辑。不指定返回值。 |
thenThrow(Throwable... throwables) | doThrow(Throwable... toBeThrown) | 调用到存根方法时抛出异常。 |
同上 | doThrow(Class extends Throwable> toBeThrown) | 调用到存根方法时抛出异常。可以定义多个异常。第一次调用到存根方法返回第一个异常。以此类推。超过异常数量的调用返回参数的最后一个异常。 |
同上 | doThrow(Class extends Throwable> toBeThrown, Class extends Throwable>... toBeThrownNext) | 同上。 |
无对应方法 | doNothing() | void方法使用的存根方式。 |
两种方式的示例:
public class Bar {public int add(int a, int b) {return a + b;}public void badCode() {throw new RuntimeException("bad bar code");}
}public class Foo {private Bar bar;public int sum(int a, int b) {return bar.add(a, b);}public int count() {bar.badCode();return 5;}
}
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {//foo 对象内部的成员变量会自动被 @Mock 注解的生成的对象注入。@InjectMocksprivate Foo foo;//bar 对象会自动的注入到 @InjectMocks 注解的对象的成员变量中去。@Mockprivate Bar bar;@Testpublic void thenTest() {Mockito.when(bar.add(1, 2)).then(new Answer
package cn.theten52.demo.maven;import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {//foo 对象内部的成员变量会自动被 @Mock 注解的生成的对象注入。@InjectMocksprivate Foo foo;//bar 对象会自动的注入到 @InjectMocks 注解的对象的成员变量中去。@Mockprivate Bar bar;@Testpublic void doAnswerTest() {Answer
注意事项:
默认情况下,对于所有方法的返回值,mock将返回 null、原始/原始包装值或空集合,具体视情况而定。例如 int/Integer 返回0,布尔值/布尔值返回false。
存根可以被覆盖:例如,普通存根可以在进入测试夹具(before方法)时设置,但在正式的测试方法中可以被覆盖。请注意,覆盖存根是一种潜在的代码异味,表示存根过多。
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {//foo 对象内部的成员变量会自动被 @Mock 注解的生成的对象注入。@InjectMocksprivate Foo foo;//bar 对象会自动的注入到 @InjectMocks 注解的对象的成员变量中去。@Mock(lenient = true)private Bar bar;@Beforepublic void before() {//此行存根被覆盖了Mockito.doReturn(6).when(bar).add(1, 2);}@Testpublic void doReturnTest() {Mockito.doReturn(7).when(bar).add(1, 2);int result = foo.sum(1, 2);Assert.assertEquals(7, result);}}
或者:
//所有的 mock.someMethod("some arg") 被调用时会返回 "two"Mockito.when(mock.someMethod("some arg")).thenReturn("one")Mockito.when(mock.someMethod("some arg")).thenReturn("two")
当mock检查到有不必须的存根时(只定义而没有使用),会抛出异常:
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected in test class: MockitoTest
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):1. -> at cn.xxx.demo.MockitoTest.before(MockitoTest.java:25)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
解决此问题可以删除不必须的存根代码,或者使用@Mock(lenient = true)标识存在不必须存根的mock对象:
@Mock(lenient = true)private Bar bar;
一旦被存根,该方法将始终返回一个存根值,无论它被调用多少次。
thenReturnTest()
、doReturnTest()
系列方法。最后的存根更重要 - 当您多次用相同的参数存根相同的方法时。超过存根次数的调用会最后存根的返回策略。换句话说:存根的顺序很**重要,**但它只是很少有意义,例如当存根完全相同的方法调用或有时使用参数匹配器时等。
thenReturn2Test()
、thenThrowTest()
、thenThrow2Test()
、doThrowTest()
、doThrow2Test()
、doReturn2Test()
系列方法。我们不仅能存根mock对象(一般指被@Mock注解的对象),还能存根spy对象(一般指被@Spy注解的对象)。
关于返回策略。请参考本系列文章【Java测试框架系列:Mockito 详解:第一部分:对象创建】。
我们知道,默认情况下,对于所有方法的返回值,mock将返回 null、原始/原始包装值或空集合,具体视情况而定。
那么我们可以在对具体方法进行存根情况下更改其返回值吗?当然可以,具体细节请参考本系列文章【Java测试框架系列:Mockito 详解:第一部分:对象创建】。
参数匹配器可以使用在方法的存根时,当我们不确定待存根的方法在被调用时的具体的值是多少时,我们就可以使用它来解决这个问题。(它还可以使用在测试结束时的验证/断言处,我们会在下一篇文章中介绍)
Mockito参数验证值时使用自然java风格:即通过使用equals()
方法进行验证。有时,当需要额外的灵活性时,您可以使用参数匹配器(ArgumentMatcher
或ArgumentCaptor
):
//使用内置的anyInt()参数匹配器创建存根when(mockedList.get(anyInt())).thenReturn("element");//使用自定义的参数匹配器isValid()when(mockedList.contains(argThat(isValid()))).thenReturn(true);//输出"element"System.out.println(mockedList.get(999));//你也使用使用参数匹配器进行验证verify(mockedList).get(anyInt());//参数匹配器也可以写成java 8 Lambdas的方式verify(mockedList).add(argThat(someString -> someString.length() > 5));
Mockito
扩展 ArgumentMatchers 以便访问所有匹配器只需静态导入 Mockito 类。
//使用anyInt()参数匹配器进行存根when(mockedList.get(anyInt())).thenReturn("element");//以下代码会打印"element"System.out.println(mockedList.get(999));//你也可以在验证时使用参数匹配器verify(mockedList).get(anyInt());
由于 Mockito对any(Class)
和anyInt
家族的匹配器执行类型检查,因此它们不会匹配null
参数。而是使用isNull
匹配器。
// 使用anyBoolean()参数匹配器进行存根when(mock.dryRun(anyBoolean())).thenReturn("state");// 以下存根不会匹配,而且不会返回"state"mock.dryRun(null);// 可以将存根改成此方式:when(mock.dryRun(isNull())).thenReturn("state");mock.dryRun(null); // ok// 或者修改代码:when(mock.dryRun(anyBoolean())).thenReturn("state");mock.dryRun(true); // ok
以上同样适用于使用了参数匹配器的验证。
提示:
当我们遇到可能为null的参数时,可以偷懒的使用any()方法进行匹配。它可以匹配null和非null值。
警告: 如果您使用参数匹配器,则所有参数都必须由匹配器提供。例如:(示例显示的是验证操作,但同样适用于方法的存根):
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));//以上代码是合适的 - eq()方法时参数匹配器verify(mock).someMethod(anyInt(), anyString(), "third argument");//以上代码不正确 - 将会抛出异常,因为第三个参数没有使用参数匹配器给出。
匹配器方法,如anyObject()
,eq()
不返回匹配器。在内部,它们在堆栈上记录一个匹配器并返回一个虚拟值(通常为空)。此实现是由于 java 编译器强加的静态类型安全。结果是您不能使用anyObject()
,eq()
方法在验证/存根之外的方法。
参数配器允许灵活的验证或存根。 查看更多内置匹配器和**自定义参数匹配器
/ Hamcrest匹配器
**的示例。(下文也有介绍)
合理使用复杂的参数匹配。偶尔使用equals()
配合 anyX()
匹配器 的自然匹配风格往往会提供干净和简单的测试。有时最好重构代码以允许equals()
匹配甚至实现equals()
方法来帮助测试。
ArgumentCaptor
是参数匹配器的一种特殊实现,它捕获参数值以进行进一步的断言。
参数匹配器的相关警告:
如果您使用参数匹配器,则所有参数都必须由匹配器提供。
以下示例展示了在验证时使用参数匹配器,但是在存根方法调用时它同样适用:
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));//上面的写法是正确的 - eq() 也是参数匹配器verify(mock).someMethod(anyInt(), anyString(), "third argument");//上面的写法是错误的 - 异常会被抛出因为第三个参数并不是参数匹配器的形式
匹配器方法如anyObject()
,eq()
不会返回匹配器。在内部,它们在堆栈上记录一个匹配器并返回一个虚拟值(通常为空)。此实现是由于 java 编译器强加的静态类型安全。结果是您不能在验证/存根之外的方法使用anyObject()
,eq()
方法。
允许ArgumentCaptor
在字段上创建注解。
例子:
public class Test{@Captor ArgumentCaptor
使用 @Captor 注释的优点之一是您可以避免与捕获复杂泛型类型相关的警告。
在实现自定义参数匹配器之前 ,了解处理非平凡参数的用例和可用选项非常重要 。这样,您可以为给定场景选择最佳方法并生成最高质量的测试(干净且可维护)。
ArgumentMatcher 允许创建自定义参数匹配器。此 API 在 Mockito 2.1.0 中进行了更改,以将 Mockito 与 Hamcrest 解耦并降低版本不兼容的风险。
对于存根或验证中使用的非平凡方法参数,您有以下选项(无特定顺序):
ArgumentMatchers.notNull()
. 有时,有一个简单的测试比一个看似有效的复杂测试更好。ArgumentCaptor
捕获参数并对其状态执行断言。当您需要验证参数时很有用。如果您需要参数匹配来进行存根,则 Captor 没有用。很多时候,此选项会通过对参数的细粒度验证来实现干净且可读的测试。ArgumentMatcher
接口并将实现传递给ArgumentMatchers.argThat(org.mockito.ArgumentMatcher)
方法来使用自定义参数匹配器。如果存根需要自定义匹配器并且可以多次重用,则此选项很有用。请注意,ArgumentMatchers.argThat(org.mockito.ArgumentMatcher)
存在自动拆箱过程中的NullPointerException。MockitoHamcrest.argThat(org.hamcrest.Matcher)
是有用的。重复使用会得到益处!请注意,MockitoHamcrest.argThat(org.hamcrest.Matcher)
存在自动拆箱过程中的NullPointerException。ArgumentMatcher
。因为ArgumentMatcher
实际上是一个功能接口。lambda 可以与ArgumentMatchers.argThat(org.mockito.ArgumentMatcher)
方法一起使用。ArgumentMatcher接口的实现可以与ArgumentMatchers.argThat(org.mockito.ArgumentMatcher)
方法一起使用。使用toString()
方法描述匹配器 - 它打印在验证错误中。
class ListOfTwoElements implements ArgumentMatcher
{public boolean matches(List list) {return list.size() == 2;}public String toString() {//打印验证错误return "[list of 2 elements]";}}List mock = mock(List.class);when(mock.addAll(argThat(new ListOfTwoElements()))).thenReturn(true);mock.addAll(Arrays.asList("one", "two"));verify(mock).addAll(argThat(new ListOfTwoElements()));
为了保持可读性,您可以提取方法,例如:
verify(mock).addAll(argThat(new ListOfTwoElements()));//替换为:verify(mock).addAll(listOfTwoElements());
在 Java 8 中,您可以将 ArgumentMatcher 视为函数式接口并使用 lambda,例如:
verify(mock).addAll(argThat(list -> list.size() == 2));
在 Matchers
的 javadoc 中阅读有关其他匹配器的更多信息。
2.1.0 迁移指南
所有现有的自定义ArgumentMatcher
实现将不再长期编译。传递 hamcrest 匹配器的所有位置的argThat()
将不再长期编译。有两种方法可以解决问题:
describeTo()
方法重构为toString()
方法。org.mockito.hamcrest.MockitoHamcrest.argThat()
代替Mockito.argThat()
。确保hamcrest依赖存在于类路径(Mockito 不再依赖于 hamcrest)。什么选择适合您?如果您不介意编译对 hamcrest 的依赖,那么选项 b) 可能适合您。您的选择应该不会产生太大影响并且是完全可逆的 - 您可以在将来选择不同的选项(并重构代码)。
Mockito 通过使用equals()
方法实现了自然的 Java 风格验证参数值。这也是推荐的匹配参数的方式,因为它使测试变得干净和简单。在某些情况下,在实际验证后对某些参数断言是有帮助的。例如:
ArgumentCaptor
捕获可变参数的示例:
//捕获变量:ArgumentCaptor
警告:建议将 ArgumentCaptor 与验证一起使用,但不要与存根一起使用。使用带有存根的 ArgumentCaptor 可能会降低测试的可读性,因为 captor 是在断言(又名验证或“then”)块之外创建的。它还会降低缺陷定位,因为如果未调用存根方法,则不会捕获任何参数。
在某种程度上 ArgumentCaptor 与自定义参数匹配器有关(请参阅ArgumentMatcher
类的javadoc )。这两种技术都可用于确保将某些参数传递给mock对象。但是,在以下情况下,ArgumentCaptor 可能更适合:
自定义参数匹配器ArgumentMatcher
通常更适合存根。
AdditionalMatchers
方法简介:
AdditionalMatchers
类提供了很少的匹配器,尽管它们在组合多个匹配器或否定必要的匹配器时可能很有用。
修饰符和类型 | 方法和说明 |
---|---|
static T | any() 匹配任何内容,包括空值和可变参数。 |
static T | any(Class type) 匹配给定类型的任何对象,不包括空值。 |
static boolean | anyBoolean() 任何boolean 或非空 Boolean |
static byte | anyByte() 任何byte 或者非空 Byte 。 |
static char | anyChar() 任何char 或者非空 Character 。 |
static Collection | anyCollection() 任何非 null Collection 。 |
static Collection | anyCollectionOf(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static double | anyDouble() 任何double 或者非空 Double 。 |
static float | anyFloat() 任何float 或者非空 Float 。 |
static int | anyInt() 任何 int或非 null Integer 。 |
static Iterable | anyIterable() 任何非 null Iterable 。 |
static Iterable | anyIterableOf(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static List | anyList() 任何非 null List 。 |
static List | anyListOf(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static long | anyLong() 任何long 或者非空 Long 。 |
static Map | anyMap() 任何非 null Map 。 |
static Map | anyMapOf(Class keyClazz, Class valueClazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static T | anyObject() 已弃用。 这将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static Set | anySet() 任何非 null Set 。 |
static Set | anySetOf(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static short | anyShort() 任何short 或者非空 Short 。 |
static String | anyString() 任何非空 String |
static T | anyVararg() 已弃用。 从 2.1.0 开始使用 any() |
static T | argThat(ArgumentMatcher matcher) 允许创建自定义参数匹配器。 |
static boolean | booleanThat(ArgumentMatcher matcher) 允许创建自定义boolean 参数匹配器。 |
static byte | byteThat(ArgumentMatcher matcher) 允许创建自定义byte 参数匹配器。 |
static char | charThat(ArgumentMatcher matcher) 允许创建自定义char 参数匹配器。 |
static String | contains(String substring) 包含给定String 子字符串的参数。 |
static double | doubleThat(ArgumentMatcher matcher) 允许创建自定义double 参数匹配器。 |
static String | endsWith(String suffix) 以给定String 后缀结尾的参数。 |
static boolean | eq(boolean value) 等于给定boolean 值的参数。 |
static byte | eq(byte value) 等于给定byte 值的参数。 |
static char | eq(char value) 等于给定char 值的参数。 |
static double | eq(double value) 等于给定double 值的参数。 |
static float | eq(float value) 等于给定float 值的参数。 |
static int | eq(int value) 等于给定int 值的参数。 |
static long | eq(long value) 等于给定long 值的参数。 |
static short | eq(short value) 等于给定short 值的参数。 |
static T | eq(T value) 等于给定值的对象参数。 |
static float | floatThat(ArgumentMatcher matcher) 允许创建自定义float 参数匹配器。 |
static int | intThat(ArgumentMatcher matcher) 允许创建自定义int 参数匹配器。 |
static T | isA(Class type) 实现给定Object 类的参数。 |
static T | isNotNull() 不是null 的判断。 |
static T | isNotNull(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static T | isNull() 是null 的判断。 |
static T | isNull(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static long | longThat(ArgumentMatcher matcher) 允许创建自定义long 参数匹配器。 |
static String | matches(Pattern pattern) 与给定正则表达式Pattern 匹配的参数。 |
static String | matches(String regex) 与给定正则表达式匹String 配的参数。 |
static T | notNull() 不是null 的判断。 |
static T | notNull(Class clazz) 已弃用。 在 Java 8 中,此方法将在 Mockito 4.0 中删除。此方法仅用于通用友好性以避免强制转换,Java 8 中不再需要此方法。 |
static T | nullable(Class clazz) 参数要么是null 或者给定类型的。 |
static T | refEq(T value, String... excludeFields) 反射性等于 (使用反射判断是否相等)给定值的对象参数,支持从类中排除所选字段。 |
static T | same(T value) 与给定值相同的对象参数。 |
static short | shortThat(ArgumentMatcher matcher) 允许创建自定义short 参数匹配器。 |
static String | startsWith(String prefix) 以给定String 前缀开头的参数。 |
Hamcrest 匹配器
允许使用 hamcrest 匹配器匹配参数。 在类路径上需要包含 hamcrest依赖,Mockito不依赖于 hamcrest!请注意下面描述的自动拆箱过程中的NullPointerException警告。
在实现或重用现有的 hamcrest 匹配器之前,请阅读如何处理ArgumentMatcher
.
Mockito 2.1.0 与 Hamcrest 分离了,以避免过去影响我们用户的版本不兼容。Mockito 提供了一个专用的 API ArgumentMatcher
来匹配参数。并提供了 Hamcrest 集成,以便用户可以利用现有的 Hamcrest 匹配器。
例子:
import static org.mockito.hamcrest.MockitoHamcrest.argThat;//进行存根when(mock.giveMe(argThat(new MyHamcrestMatcher())));//验证verify(mock).giveMe(argThat(new MyHamcrestMatcher()));
自动拆箱过程中的NullPointerException
警告:在极少数情况下,当匹配原始参数类型时,您必须使用 intThat()、floatThat() 等相关的方法。这样你就可以避免在自动拆箱过程中的NullPointerException
。由于 java 的工作方式,我们实际上并没有一种干净的方法来检测这种情况并保护用户免受此问题的影响。希望这段文字能很好地描述问题和解决方案。如果您知道如何解决问题,请通过邮件列表或问题跟踪器告诉我们。
修饰符和类型 | 方法和说明 |
---|---|
static T | argThat(org.hamcrest.Matcher matcher) 允许使用 hamcrest 匹配器匹配参数。 |
static boolean | booleanThat(org.hamcrest.Matcher matcher) 启用与原始boolean 参数匹配的集成 hamcrest 匹配器。 |
static byte | byteThat(org.hamcrest.Matcher matcher) 启用与原始byte 参数匹配的集成 hamcrest 匹配器。 |
static char | charThat(org.hamcrest.Matcher matcher) 启用与原始char 参数匹配的集成 hamcrest 匹配器。 |
static double | doubleThat(org.hamcrest.Matcher matcher) 启用与原始double 参数匹配的集成 hamcrest 匹配器。 |
static float | floatThat(org.hamcrest.Matcher matcher) 启用与原始float 参数匹配的集成 hamcrest 匹配器。 |
static int | intThat(org.hamcrest.Matcher matcher) 启用与原始int 参数匹配的集成 hamcrest 匹配器。 |
static long | longThat(org.hamcrest.Matcher matcher) 启用与原始long 参数匹配的集成 hamcrest 匹配器。 |
static short | shortThat(org.hamcrest.Matcher matcher) 启用与原始short 参数匹配的集成 hamcrest 匹配器。 |
最后小编在学习过程中整理了一些学习资料,可以分享给做软件测试工程师的朋友们,相互交流学习,需要的可以加入我的学习交流群 323432957 或加微dingyu-002即可免费获取Python自动化测开及Java自动化测开学习资料(里面有功能测试、性能测试、python自动化、java自动化、测试开发、接口测试、APP测试等多个知识点的架构资料)