Mockito教程一

1.验证一些属性

因为大部分人都很熟悉这些接口(例如add(),get(),clear()等方法),下面是一个mock个List的例子。

在实际情况下,请直接使用List真实实例,而不要去mock。

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

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

 //using mock object
 // 像List真实实例一样,使用mock对象
 mockedList.add("one");
 mockedList.clear();

 //verification
 // 作些验证
 verify(mockedList).add("one");
 verify(mockedList).clear();

当一个mock实例被创建后,它会记录所有交互行为。然后,你可以根据需要进行验证。

2.如何进行打桩(How about some stubbing?)

 //You can mock concrete classes, not just interfaces
 // 不仅是接口,也可以mock具体类
 LinkedList mockedList = mock(LinkedList.class);

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

 //following prints "first"
 // 下面代码将会打印"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
 // 下面语句,由于没有进行打桩,就会得到null
 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).
 // 如果你的代码关心get(0)的返回,然后,一些事情会被打破(这通常是在verify()函数被调用前)
 //If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
 // 如果你的代码不关心get(0)的返回,便不需要打桩了。不服,那就看这里
 verify(mockedList).get(0);

默认情况下,所有有返回值的方法,一个mock对象都会返回一个适当值,比如null,或者内置类型或内置类型的装箱值,或者一个空的集合。例如,0作为返回值为int/Integer的返回,false,作为返回值为boolean/Boolean的返回;

桩函数可以被重写:例如,公共桩函数可以用来安装测试夹具(fixture setup),但测试方法可以重写它。请记住重新装函数,通常是表示有潜在代码被打桩太多的味道;

一旦被打桩(stubbed),则方法会总是返回桩值,无论方法被调用多少次。上面的原则很重要——当你对一些方法多次用同一个值进行打桩时。换句话说:打桩的顺序是有关系的(the order of stubbing matters),一般情况下意义不大,只有在桩(stubbing)恰好是同样方法的调用或者使用参数参数匹配器(argument matchers)时。

3.参数匹配器 (Argument matchers)

Mockito通常用Java语法方式的equals()函数来验证参数值。有些时候,当你需要额外的灵活性时,你也可以用参数匹配器

//stubbing using built-in anyInt() argument matcher
// 使用内置的anyInt()参数匹配器来打桩
 when(mockedList.get(anyInt())).thenReturn("element");

 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 // 使用自定义参数匹配器(让我们把这个返回自定义匹配的实现叫做isValid())
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");

 //following prints "element"
 // 下面会打印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
 // 参数匹配器也可以使用Java 8 的Lambdas表达式来定义
 verify(mockedList).add(someString -> someString.length() > 5);

参数匹配器增加了验证和打桩的灵活性,点击这里这里可以查看更多内置匹配器和自定义参数匹配器

关于自定义匹配器的信息,可以参考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
   // 上面代码是正确的,eq也是一个匹配器

   verify(mock).someMethod(anyInt(), anyString(), "third argument");
   //above is incorrect - exception will be thrown because third argument is given without an argument matcher.
   // 上面的代码是有问题的,因为第三个参数不是匹配器,所以,会抛出异常

像anyObject(),eq()这样的匹配器方法并不会返回匹配器。在这些方法内部,会在栈上记录一个匹配,然后返回一个傀儡值(通常是null)。这些实现是因为Java编译器的静态类型安全。这种实现的后果就是,你不能在verified/stubbed方法以外的地方使用anyObject(), eq()这些方法。

4. 验证确切次数调用/至少X次/或者没有调用(Verifying exact number of invocations/at least x/never)

 //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
 // 下面的两个验证,验证次数——默认是times(1)表示一次
 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)
 // 验证重没有调用,never()是times(0)的一个别名
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost()
 // 至少/最多调用次数的验证
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("five times");
 verify(mockedList, atMost(5)).add("three times");

time(1)是默认值,因此,time(1)是可以省略的。

5.通过异常对void方法打桩(Stubbing void methods with exceptions)

doThrow(new RuntimeException()).when(mockedList).clear();

//following throws
// 下面的调用会抛出异常
RuntimeException:
   mockedList.clear();

有关doThrow或doAnswer函数族的更多信息,可以参考12节

验证次序

// A. Single mock whose methods must be invoked in a particular order
// 方式A:一个方法被按照指定次序调用的简单mock
 List singleMock = mock(List.class);

 //using a single mock
 // 使用上面的mock
 singleMock.add("was added first");
 singleMock.add("was added second");

 //create an inOrder verifier for a single mock
 // 为single mock创建一个InOrder
 InOrder inOrder = inOrder(singleMock);

 //following will make sure that add is first called with "was added first, then with "was added second"
 // 下面的代码会验证add函数第一次调用参数为"was added first",然后参数是"was added second"
 inOrder.verify(singleMock).add("was added first");
 inOrder.verify(singleMock).add("was added second");

 // B. Multiple mocks that must be used in a particular order
 // 方式B:多个mock对象的按次序使用
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);

 //using mocks
 firstMock.add("was called first");
 secondMock.add("was called second");

 //create inOrder object passing any mocks that need to be verified in order
 // 使用多个mock对象的调用次序创建一个InOrder
 InOrder inOrder = inOrder(firstMock, secondMock);

 //following will make sure that firstMock was called before secondMock
 // 下面的代码验证firstMock对象的add调用是先于secondMock对象
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");

 // Oh, and A + B can be mixed together at will
 // 当然,A和B方式是可以混合在一起使用的

验证次序是灵活的,你不用把每个方法的调用都验证一遍,你只需要验证那些你真正需要的。还有,你也可以仅仅使用那些需要验证次序的mock对象来创建InOrder对象。

7. 确保mock无交互行为发生(Making sure interaction(s) never happened on mock)

 //using mocks - only mockOne is interacted
 // 
 mockOne.add("one");

 //ordinary verification
 // 普通验证
 verify(mockOne).add("one");

 //verify that method was never called on a mock
 // 验证一个mock的方法永远没有调用
 verify(mockOne, never()).add("two");

 // verify that other mocks were not interacted
 // 确保其他mock对象没有交互行为发生
 verifyZeroInteractions(mockTwo, mockThree);

8. 检测冗余调用(Finding redundant invocations)

//using mocks
 mockedList.add("one");
 mockedList.add("two");

 verify(mockedList).add("one");

 //following verification will fail
 // 下面的验证会失败
 verifyNoMoreInteractions(mockedList);

警告:
经常使用古典方式,预期-运行-验证,的人倾向于频繁使用verifyNoMoreInteractions(),甚至在每个测试方法里都使用。但verifyNoMoreInteractions()并不被推荐在每个方法里都使用。verifyNoMoreInteractions()是互动测试工具包里一个便利的断言。仅仅在你需要验证冗余调用的时候使用。滥用verifyNoMoreInteractions()会使测试代码很难维护。
never()是一种更加显示和意图明确的方式。

以上内容翻译自Mockito官方教程的1-8节

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mockito是一个用于Java的开源测试框架,用于创建和管理模拟对象(mock objects)。它可以帮助我们进行单元测试,特别是在测试依赖对象时非常有用。 在Mockito中,我们可以使用注解来简化模拟对象的创建。例如,使用`@Mock`注解可以创建一个模拟对象,使用`@InjectMocks`注解可以将模拟对象注入到被测试对象中。 Mockito还提供了一些方法来验证模拟对象的交互和行为。例如,使用`verify`方法可以验证方法是否被调用,使用`times`方法可以指定方法被调用的次数,使用`never`方法可以验证方法是否从未被调用。 另外,Mockito还支持设置模拟对象的行为。我们可以使用`when`方法来设置模拟对象方法的返回值,使用`doReturn`方法来设置模拟对象方法的行为。 总之,Mockito是一个强大的测试框架,可以帮助我们进行单元测试,并且使用注解可以简化模拟对象的创建。通过验证和设置模拟对象的行为,我们可以更好地测试我们的代码。 #### 引用[.reference_title] - *1* *2* *3* [【码农教程】手把手教你Mockito的使用](https://blog.csdn.net/AI_Green/article/details/129163693)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值