单元测试系列之Mockito详解(下)

发表于:2022-3-15 10:00

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

 作者:大师傅姑爷    来源:稀土掘金

  指定返回值
  假设上面的LoginPresenter的login()方法是这么实现的:
  public void login(String username, String password) {
         if (username == null || username.length() == 0) return;
         mPasswordValidator = new PasswordValidator();
         if (!mPasswordValidator.verifyPassword(password)) return;
         mUserManager.performLogin(username, password);
    }

  在登陆时,需要验证密码的正确性,而此时可能需要联网验证,所以会很耗时。在测试时就可以简单处理,比如直接返回true或者false。因为测试的是login()方法,和验证密码的内部逻辑没有关系,所以可以这么做,这才是单元测试该有的粒度。
  指定mock对象的某个方法,让它返回特定值的写法如下:
  Mockito.when(mockObject.targetMethod(args)).thenReturn(desiredReturnValue);

  所以我们可以这样写:
  //先创建一个mock对象
  PasswordValidator mockValidator = Mockito.mock(PasswordValidator.class);
  //当调用mockValidator的verifyPassword方法,同时传入"123"时,返回true
  Mockito.when(mockValidator.verifyPassword("123")).thenReturn(true);
  //当调用mockValidator的verifyPassword方法,同时传入"123456"时,返回false
  Mockito.when(validator.verifyPassword("123456")).thenReturn(false);

  由于已经指定了返回值,所以并不会执行verifyPassword()方法的真实逻辑!
  此时,测试类可以这样写:
  public class LoginPresenterTest {
     LoginPresenter loginPresenter;
     @Test
     public void testLogin() {
         UserManager mockUserManager = Mockito.mock(UserManager.class);
         PasswordValidator mPasswordValidator = Mockito.mock(PasswordValidator.class);
         Mockito.when(mPasswordValidator.verifyPassword(Mockito.anyString())).thenReturn(true);//当thenReturn(false)时,测试失败,因为mUserManager.performLogin(username, password)代码不会执行
   
         loginPresenter = new LoginPresenter(mockUserManager, mPasswordValidator);
         loginPresenter.login("aya", "123");
         Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("aya", "123");
    }
  }

  注意:mock出来的对象mPasswordValidator一定要传进去!
  执行特定动作
  假设我们的LoginPresenter的login()方法是这样的:
  public void login(String username, String password) {
         if (username == null || username.length() == 0) return;
         if (!mPasswordValidator.verifyPassword(password)) return;
         mUserManager.performLogin(username, password, new UserManager.NetCallback() {
             @Override
             public void onSuccess(Object data) {
                 //登陆成功,用数据更新UI
   
            }
   
             @Override
             public void onFailure(String msg) {
                 //登陆失败,显示msg
   
            }
        });
    }

  在这里,我们想进一步测试传给mUserManager.performLogin的NetCallback里面的代码,验证view得到了更新等等。在测试环境下,我们并不想依赖mUserManager.performLogin的真实逻辑,而是让mUserManager直接调用传入的NetCallback的onSuccess或onFailure方法。这种指定mock对象执行特定的动作的写法如下: Mockito.doAnswer(desiredAnswer).when(mockObject).targetMethod(args); 传给doAnswer()的是一个Answer对象,我们想要执行什么样的动作,就在这里面实现。结合上面的例子解释:
  public class LoginPresenterTest {
     LoginPresenter loginPresenter;
     @Test
     public void testLogin() {
         UserManager mockUserManager = Mockito.mock(UserManager.class);
         PasswordValidator mPasswordValidator = Mockito.mock(PasswordValidator.class);
         Mockito.when(mPasswordValidator.verifyPassword(Mockito.anyString())).thenReturn(true);
   
         loginPresenter = new LoginPresenter(mockUserManager, mPasswordValidator);
   
         Mockito.doAnswer(new Answer() {
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
                 //这里可以获得传给performLogin的参数
                 Object[] arguments = invocation.getArguments();
   
                 //callback是第三个参数
                 UserManager.NetCallback callback = (UserManager.NetCallback) arguments[2];
                 callback.onFailure("404 Not found");
                 return 404;
            }
        }).when(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString(), Mockito.any(UserManager.NetCallback.class));
   
         loginPresenter.login("aya", "123456");
    }
  }

  当调用mockUserManager的performLogin方法时,会执行answer里面的代码,我们上面的例子是直接调用传入的callback的onFailure方法,同时传给onFailure方法404和"Not found"。
  除了doAnswer(),mockito还提供了doNothing(),用来指定目标方法“什么都不做”;doThrow(desiredException),指定目标方法“抛出一个异常”;doCallRealMethod(),让目标方法调用真实的逻辑。
  Spy
  对于一个mock对象,我们可以指定返回值和执行特定的动作,当然,也可以不指定,如果不指定的话,一个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。
  如果你想实现这样的效果:指定时执行指定的动作,不指定时调用这个对象的默认实现,同时又能拥有验证方法调用的功能。那你可以使用Mockito.spy()来创建对象。
  创建一个spy对象,以及spy对象的用法介绍如下:
  public class PasswordValidator {
     public boolean verifyPassword(String password) {
         if (password == null || password.length() < 6) {
             return false;
        }
         return true;
    }
  }
   
  public class PasswordValidatorTest {
     @Test
     public void testSpy(){
         PasswordValidator mockPasswordValidator = Mockito.mock(PasswordValidator.class);
         boolean b0 = mockPasswordValidator.verifyPassword("1234567");//不指定时mock对象返回默认值
         System.out.println("测试mock默认行为: " + b0);
   
         Mockito.when(mockPasswordValidator.verifyPassword("1234567")).thenReturn(true);//mock对象指定行为:返回true
         boolean b1 = mockPasswordValidator.verifyPassword("1234567");//指定时mock对象返回指定值
         System.out.println("测试mock指定行为: " + b1);
   
         PasswordValidator spyPasswordValidator = Mockito.spy(PasswordValidator.class);
         boolean b2 = spyPasswordValidator.verifyPassword("1234567");//不指定时spy对象调用真实逻辑
         System.out.println("测试spy默认行为: " + b2);
   
         Mockito.when(spyPasswordValidator.verifyPassword("1234567")).thenReturn(false);//spy对象指定行为:返回false
         boolean b3 = spyPasswordValidator.verifyPassword("1234567");//指定时spy对象返回指定值
         System.out.println("测试spy指定行为: " + b3);
       
         Mockito.verify(spyPasswordValidator).verifyPassword("1234567");//测试PasswordValidator.verifyPassword("1234567")调用次数,结果失败,因为上面已经调用了两次passwordValidator.verifyPassword("1234567")
   
    }
  }

  运行测试方法testSpy(),输出结果如下:
  通过对比可以看出:spy与mock的唯一区别就是默认行为不一样:spy对象的方法默认调用真实的逻辑,mock对象的方法默认什么都不做,或直接返回默认值。

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

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号