我是靠谱客的博主 糊涂手机,最近开发中收集的这篇文章主要介绍Mockito 常见操作案例1. 验证某些行为2. 做测试桩(Stub)3. 参数匹配器(matchers)4. 验证函数的确切、最少、从未调用次数5. 为返回值为void的函数通过Stub抛出异常6. 验证执行顺序7. 确保交互(interaction)操作不会执行在mock对象上8. 查找冗余的调用9. 简化mock对象的创建 10. 为连续的调用做测试桩(stub)11. 为回调做测试桩 Answer12. doReturn()、doThrow()、doAnswer()、doNothing,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

案例

1. 验证某些行为

2. 做测试桩(Stub)

3. 参数匹配器(matchers)

4. 验证函数的确切、最少、从未调用次数

5. 为返回值为void的函数通过Stub抛出异常

6. 验证执行顺序

7. 确保交互(interaction)操作不会执行在mock对象上

8. 查找冗余的调用

9. 简化mock对象的创建

 10. 为连续的调用做测试桩(stub)

11. 为回调做测试桩 Answer

12. doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法的运用

 13. 监控真实对象 spy

14. 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )

15. 为下一步的断言捕获参数 (1.8版本之后)

16. 真实的局部mocks (1.8版本之后)

17. 重置mocks对象 (1.8版本之后)

18. 行为驱动开发的别名 (1.8版本之后)

19. 注解:@Captor,@Spy,@ InjectMocks (1.8.3版本之后)

20. 验证超时 (1.8.5版本之后)


案例

public class Person {
	private final int id;
	private final String name;

	public Person(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}
}
public interface PersonDao {
	Person getPerson(int id);
	boolean update(Person person);
}

public class PersonService {
    private final PersonDao personDao;
  
    public PersonService(PersonDao personDao) {
        this.personDao = personDao;
    }

    public boolean update(int id, String name) {
        Person person = personDao.getPerson(id);
        if (person == null) {
            return false;
        }
        Person personUpdate = new Person(person.getId(), name);
        return personDao.update(personUpdate);
    }
}
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
这里使用了两个参数匹配器:
  isA():Object argument that implements the given class.
  eq():int argument that is equal to the given value
注:Mockito使用verify去校验方法是否被调用,然后使用isA和eq这些内置的参数匹配器可以更加灵活,
*/
public class PersonServiceTest {
    private PersonDao mockDao;
    private PersonService personService;

    @Before
    public void setUp() throws Exception {
        //模拟PersonDao对象
        mockDao = mock(PersonDao.class);
        when(mockDao.getPerson(1)).thenReturn(new Person(1, "Person1"));
        when(mockDao.update(isA(Person.class))).thenReturn(true);

        personService = new PersonService(mockDao);
    }

    @Test
    public void testUpdate() throws Exception {
        boolean result = personService.update(1, "new name");
        assertTrue("must true", result);
        //验证是否执行过一次getPerson(1)
        verify(mockDao, times(1)).getPerson(eq(1));
        //验证是否执行过一次update
        verify(mockDao, times(1)).update(isA(Person.class));
    }

    @Test
    public void testUpdateNotFind() throws Exception {
        boolean result = personService.update(2, "new name");
        assertFalse("must true", result);
        //验证是否执行过一次getPerson(1)
        verify(mockDao, times(1)).getPerson(eq(1));
        //验证是否执行过一次update
        verify(mockDao, never()).update(isA(Person.class));
    }
}

1. 验证某些行为

// 创建mock对象, mock一个List接口
List mockedList = mock(List.class);
        
// 使用mock对象
mockedList.add("one");
mockedList.clear();

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

2. 做测试桩(Stub)

// 你可以mock一个具体的类型, 而不仅是接口
LinkedList mockedList = mock(LinkedList.class);

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

// 输出"first"
System.out.println(mockedList.get(0));

// 抛出异常
System.out.println(mockedList.get(1));

// 因为get(999)没有打桩, 因此输出null
System.out.println(mockedList.get(999));

// 验证get(0)被调用次数
verify(mockedList).get(0);

3. 参数匹配器(matchers)

参数匹配器的注意点 :
如果你使用参数匹配器, 所有参数都必须由匹配器提供。

// 使用内置的anyInt()参数匹配器
when(mockedList.get(anyInt())).thenReturn("element");

// 使用自定义的参数匹配器(在 isValid() 函数中返回你自己的匹配器实现)
when(mockedList.contains(argThat(isValid()))).thenReturn("element");

// 输出element
System.out.println(mockedList.get(999));

// 也可以验证参数匹配器
verify(mockedList).get(anyInt());

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的, 因为eq()也是一个参数匹配器

verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代码是错误的,因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此的缘故会抛出异常

4. 验证函数的确切、最少、从未调用次数

 mockedList.add("once");

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

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

 // 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 // 验证具体的执行次数
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 // 使用never()进行验证,never相当于times(0)
 verify(mockedList, never()).add("never happened");

 // 使用atLeast()/atMost()
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("five times");
 verify(mockedList, atMost(5)).add("three times");

5. 为返回值为void的函数通过Stub抛出异常

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

// 调用这句代码会抛出异常
mockedList.clear();

6. 验证执行顺序

// A. 验证mock一个对象的函数执行顺序
List singleMock = mock(List.class);

singleMock.add("was added first");
singleMock.add("was added second");

// 为该mock对象创建一个inOrder对象
InOrder inOrder = inOrder(singleMock);

// 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");

// B.验证多个mock对象的函数执行顺序
List firstMock = mock(List.class);
List secondMock = mock(List.class);

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

// 为这两个Mock对象创建inOrder对象
inOrder = inOrder(firstMock, secondMock);

// 验证它们的执行顺序
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

7. 确保交互(interaction)操作不会执行在mock对象上

 // 使用Mock对象
 mockOne.add("one");

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

 // 验证某个交互是否从未被执行
 verify(mockOne, never()).add("two");

 // 验证mock对象没有交互过
 verifyZeroInteractions(mockTwo, mockThree);

8. 查找冗余的调用

一些用户可能会在频繁地使用verifyNoMoreInteractions(),甚至在每个测试函数中都用。但是verifyNoMoreInteractions()并不建议在每个测试函数中都使用。
verifyNoMoreInteractions()在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时。滥用它将导致测试代码的可维护性降低。never()是一种更为明显且易于理解的形式。

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

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

// 下面的验证将会失败
verifyNoMoreInteractions(mockedList);

9. 简化mock对象的创建

注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中:

MockitoAnnotations.initMocks(testClass);

你可以使用内置的runner: MockitoJUnitRunner runner 或者一个rule : MockitoRule。 关于mock注解的更多信息可以阅读MockitoAnnotations文档。

public class ArticleManagerTest {
  @Mock private ArticleCalculator calculator;
  @Mock private ArticleDatabase database;
  @Mock private UserProvider userProvider;

  private ArticleManager manager;

  @Before public void setup() {
	  manager = new ArticleManager(userProvider, database, calculator);
  }
}

 10. 为连续的调用做测试桩(stub)

 when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

 // 第一次调用 : 抛出运行时异常
 mock.someMethod("some arg");

 // 第二次调用 : 输出"foo"
 System.out.println(mock.someMethod("some arg"));

 // 后续调用 : 也是输出"foo"
 System.out.println(mock.someMethod("some arg"));

// 第一次调用时返回"one",第二次返回"two",第三次返回"three"
 when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");

11. 为回调做测试桩 Answer

运行为泛型接口Answer打桩。Answer 是个泛型接口。到调用发生时将执行这个回调。
在最初的Mockito里也没有这个具有争议性的特性。我们建议使用thenReturn() 或thenThrow()来打桩。这两种方法足够用于测试或者测试驱动开发。

 when(mock.someMethod(anyString())).thenAnswer(new Answer() {
     Object answer(InvocationOnMock invocation) {
         Object[] args = invocation.getArguments();
         Object mock = invocation.getMock();	// 拿到mock对象
         return "called with arguments: " + args;
     }
 });

 // 输出 : "called with arguments: foo"
 System.out.println(mock.someMethod("foo"));

12. doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法的运用

通过when(Object)为无返回值的函数打桩有不同的方法,因为编译器不喜欢void函数在括号内...
使用doThrow(Throwable) 替换stubVoid(Object)来为void函数打桩是为了与doAnswer()等函数族保持一致性。
当你想为void函数打桩时使用含有一个exception 参数的doAnswer() :

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

// 下面的代码会抛出异常
mockedList.clear();

当你调用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时可以在适当的位置调用when()函数. 当你需要下面这些功能时这是必须的:

  • 测试void函数
  • 在受监控的对象上测试函数
  • 不止一次的测试为同一个函数,在测试过程中改变mock对象的行为。

 13. 监控真实对象 spy

你可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。尽量少使用spy对象,使用时也需要小心形式,例如spy对象可以用来处理遗留代码。
监控一个真实的对象可以与“局部mock对象”概念结合起来。在1.8之前,mockito的监控功能并不是真正的局部mock对象。原因是我们认为局部mock对象的实现方式并不好。

List list = new LinkedList();
List spy = spy(list);

// 你可以为某些函数打桩
when(spy.size()).thenReturn(100);

// 通过spy对象调用真实对象的函数
spy.add("one");
spy.add("two");

// 输出第一个元素
System.out.println(spy.get(0));

// 因为size()函数被打桩了,因此这里返回的是100
System.out.println(spy.size());

// 交互验证
verify(spy).add("one");
verify(spy).add("two");

有时,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。因此,当使用监控对象时请考虑doReturn|Answer|Throw()函数族来进行打桩。例如 :

List list = new LinkedList();
List spy = spy(list);

// 不可能 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
when(spy.get(0)).thenReturn("foo");

// 你需要使用doReturn()来打桩
doReturn("foo").when(spy).get(0);

Mockito并不会为真实对象代理函数调用,实际上它会拷贝真实对象。因此如果你保留了真实对象并且与之交互,不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被stub的函数时并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果。

因此结论就是 : 当你在监控一个真实对象时,你想在stub这个真实对象的函数,那么就是在自找麻烦。或者你根本不应该验证这些函数。

14. 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )

Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
Foo mockTwo = mock(Foo.class, new YourOwnAnswer());

15. 为下一步的断言捕获参数 (1.8版本之后)

ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
// 参数捕获
verify(mock).doSomething(argument.capture());
// 使用equal断言
assertEquals("John", argument.getValue().getName());

16. 真实的局部mocks (1.8版本之后)

List list = spy(new LinkedList());

Foo mock = mock(Foo.class);

when(mock.someMethod()).thenCallRealMethod();

17. 重置mocks对象 (1.8版本之后)

List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);

reset(mock);
//at this point the mock forgot any interactions & stubbing

18. 行为驱动开发的别名 (1.8版本之后)

import static org.mockito.BDDMockito.*;

Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);

public void shouldBuyBread() throws Exception {
  //given
  given(seller.askForBread()).willReturn(new Bread());
  //when
  Goods goods = shop.buyBread();
  //then
  assertThat(goods, containBread());
}

19. 注解:@Captor,@Spy,@ InjectMocks (1.8.3版本之后)

V1.8.3 带来的新注解在某些场景下可能会很实用
@Captor  简化 ArgumentCaptor 的创建 - 当需要捕获的参数是一个令人讨厌的通用类,而且你想避免编译时警告。 
@Spy  - 你可以用它代替 spy(Object) 方法 
@InjectMocks  - 自动将模拟对象或侦查域注入到被测试对象中。需要注意的是 @InjectMocks  也能与 @Spy  一起使用,这就意味着 Mockito 会注入模拟对象到测试的部分测试中。它的复杂度也是你应该使用部分测试原因。 
所有新的注解仅仅在 MockitoAnnotations.initMocks(Object) 方法中被处理,就像你在 built-in runner 中使用的 @Mock  注解:MockitoJUnitRunner 或 规范: MockitoRule. 

20. 验证超时 (1.8.5版本之后)

 //passes when someMethod() is called within given time span
 verify(mock, timeout(100)).someMethod();
 //above is an alias to:
 verify(mock, timeout(100).times(1)).someMethod();

 //passes when someMethod() is called *exactly* 2 times within given time span
 verify(mock, timeout(100).times(2)).someMethod();

 //passes when someMethod() is called *at least* 2 times within given time span
 verify(mock, timeout(100).atLeast(2)).someMethod();

 //verifies someMethod() within given time span using given verification mode
 //useful only if you have your own custom verification modes.
 verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod();

相关文章 Mock 实战https://blog.csdn.net/sugelachao/article/details/124278939

最后

以上就是糊涂手机为你收集整理的Mockito 常见操作案例1. 验证某些行为2. 做测试桩(Stub)3. 参数匹配器(matchers)4. 验证函数的确切、最少、从未调用次数5. 为返回值为void的函数通过Stub抛出异常6. 验证执行顺序7. 确保交互(interaction)操作不会执行在mock对象上8. 查找冗余的调用9. 简化mock对象的创建 10. 为连续的调用做测试桩(stub)11. 为回调做测试桩 Answer12. doReturn()、doThrow()、doAnswer()、doNothing的全部内容,希望文章能够帮你解决Mockito 常见操作案例1. 验证某些行为2. 做测试桩(Stub)3. 参数匹配器(matchers)4. 验证函数的确切、最少、从未调用次数5. 为返回值为void的函数通过Stub抛出异常6. 验证执行顺序7. 确保交互(interaction)操作不会执行在mock对象上8. 查找冗余的调用9. 简化mock对象的创建 10. 为连续的调用做测试桩(stub)11. 为回调做测试桩 Answer12. doReturn()、doThrow()、doAnswer()、doNothing所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部