Android单元测试之Mockito测试框架的使用(二)

发表于:2021-1-11 09:46

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:chenxibobo    来源:博客园

  5. 参数匹配器 (matchers)
  Mockito以自然的java风格来验证参数值: 使用equals()函数。有时,当需要额外的灵活性时你可能需要使用参数匹配器,也就是argument 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());
  常用的参数匹配器:
  一些示例代码:
      @Test
      public void testPersonAny(){
          when(mPerson.eat(any(String.class))).thenReturn("米饭");
          //或:
          when(mPerson.eat(anyString())).thenReturn("米饭");
          //输出米饭
          System.out.println(mPerson.eat("面条"));
      }
      @Test
      public void testPersonContains(){
          when(mPerson.eat(contains("面"))).thenReturn("面条");
          //输出面条
          System.out.println(mPerson.eat("面"));
      }
      @Test
      public void testPersonArgThat(){
          //自定义输入字符长度为偶数时,输出面条。
          when(mPerson.eat(argThat(new ArgumentMatcher<String>() {
              @Override
              public boolean matches(String argument) {
                  return argument.length() % 2 == 0;
              }
          }))).thenReturn("面条");
          //输出面条
          System.out.println(mPerson.eat("1234"));
      }
  需要注意的是,如果你打算使用参数匹配器,那么所有参数都必须由匹配器提供。例如:
  verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
  // 上述代码是正确的,因为eq()也是一个参数匹配器
  verify(mock).someMethod(anyInt(), anyString(), "third argument");
  // 上述代码是错误的, 因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常
  像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。
  6. 使用InOrder验证执行执行顺序
  验证执行执行顺序主要使用InOrder函数
  如,验证mock一个对象的函数执行顺序:
      @Test
      public void testInorder() {
          List<String> singleMock = mock(List.class);
          singleMock.add("小明");
          singleMock.add("小红");
          // 为该mock对象创建一个inOrder对象
          InOrder inOrder = inOrder(singleMock);
          // 验证add函数首先执行的是add("小明"),然后才是add("小红"),否则测试失败
          inOrder.verify(singleMock).add("小明");
          inOrder.verify(singleMock).add("小红");
      }
  验证多个mock对象的函数执行顺序:
      @Test
      public void testInorderMulti() {
          List<String> firstMock = mock(List.class);
          List<String> secondMock = mock(List.class);
          firstMock.add("小明");
          secondMock.add("小红");
          // 为这两个Mock对象创建inOrder对象
          InOrder inOrder = inOrder(firstMock, secondMock);
          // 验证它们的执行顺序
          inOrder.verify(firstMock).add("小明");
          inOrder.verify(secondMock).add("小红");
      }
  验证执行顺序是非常灵活的,你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。 你可以选择单个mock对象和多个mock对象混合着来,也可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。
  7. 使用Spy监控真实对象
  监控真实对象使用spy()函数生成,或者也可以像@Mock那样使用@Spy注解来生成一个监控对象, 当你你为真实对象创建一个监控(spy)对象后,在你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。尽量少使用spy对象,使用时也需要小心形式。
      @Test
      public void testSpy() {
          List<String> list = new ArrayList<>();
          List<String> spy = spy(list);
          // 你可以选择为某些函数打桩
          when(spy.size()).thenReturn(100);
          // 调用真实对象的函数
          spy.add("one");
          spy.add("two");
          // 输出第一个元素"one"
          System.out.println(spy.get(0));
          // 因为size()函数被打桩了,因此这里返回的是100
          System.out.println(spy.size());
          // 验证交互
          verify(spy).add("one");
          verify(spy).add("two");
      }
  使用@Spy生成监控对象:
      @Spy
      Person mSpyPerson;
      @Test
      public void testSpyPerson() {
      //将会输出Person 类中getName()的真实实现,而不是null
          System.out.println(mSpyPerson.getName());
      }
  理解监控真实对象非常重要!有时,在监控对象上使用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);
  8. 使用ArgumentCaptor进行参数捕获
  参数捕获主要为了下一步的断言做准备,示例代码:
      @Test
      public void argumentCaptorTest() {
          List<Object> mock = mock(List.class);
          mock.add("John");
   //构建要捕获的参数类型,这里是String
          ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
          //在verify方法的参数中调用argument.capture()方法来捕获输入的参数
          verify(mock).add(argument.capture());
          //验证“John”参数捕获
          assertEquals("John", argument.getValue());
      }

      @Test
      public void argumentCaptorTest2() {
          List<Object> mock = mock(List.class);
          mock.add("Brian");
          mock.add("Jim");
          ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
          verify(mock, times(2)).add(argument.capture());
          //如果又多次参数调用,argument.getValue()捕获到的是最后一次调用的参数
          assertEquals("Jim", argument.getValue());
          //如果要获取所有的参数值可以调用argument.getAllValues()
          assertArrayEquals(new Object[]{"Brian","Jim"}, argument.getAllValues().toArray());
      }
  9. 使用@InjectMocks自动注入依赖对象
  有时我们要测试的对象内部需要依赖另一个对象,例如:
  public class User {
      private Address address;
      public void setAddress(Address address) {
          this.address = address;
      }
      public String getAddress() {
          return address.getDetail();
      }
  }

  public class Address {
      public String getDetail() {
          return "detail Address";
      }
  }
  User类内部需要依赖Address类,当我们测试的时候需要mock出这两个对象,然后将Address对象传入到User当中,这样如果依赖的对象多了的话就相当麻烦,Mockito 提供了可以不用去手动注入对象的方法,首先使用@InjectMocks注解需要被注入的对象,如User,然后需要被依赖注入的对象使用@Mock或@Spy注解,之后Mockito 会自动完成注入过程,例如:
      @InjectMocks
      User mTestUser;
      @Mock
      Address mAddress;
      @Test
      public void argumentInjectMock() {
          when(mAddress.getDetail()).thenReturn("浙江杭州");
          System.out.println(mTestUser.getAddress());
      }
  这样就不用关心为User 设置Address ,只要为User需要依赖的类添加注解就可以了,然后直接将重点放到测试方法的编写上。
  或者使用@Spy监控真实对象注入也可以:
      @InjectMocks
      User mTestUser;
      @Spy
      Address mAddress;
      @Test
      public void argumentInjectMock() {
          //  when(mAddress.getDetail()).thenReturn("浙江杭州");
          System.out.println(mTestUser.getAddress());
      }
  其他:
  连续调用的另一种更简短的版本:
  // 第一次调用时返回"one",第二次返回"two",第三次返回"three"
   when(mock.someMethod("some arg")).thenReturn("one", "two", "three");

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号