浅谈Android单元测试(2)——Mockito开发中的应用

    上篇已经介绍了单元测试,Junit可以验证有返回值的测试方法,但是没有返回值的是无法验证的,比如下面代码:

  @Override public void checkLoginPwd(String loginpwd) {
    if (mView.isActive()) {
      if (TextUtils.isEmpty(loginpwd)) {
        mView.showText(mView.getRes().getString(R.string.loginpwd_notnull));
        return;
      }
      if (loginpwd.length() < LOGINPWD_LENGTH_8) {
        mView.showText("登录密码必须大于8位数");
        return;
      }
        mView.gotoLoginPwdConfirmFragment();
  }
    上面的方法因为没有返回值,就不能Junit进行单元测试了,我们只好用Mockito验证gotoLoginPwdConfirmFragment()是否被调用过。这段代码在MVP的P层,只涉及业务逻辑,与V层UI隔离。V层单元测试我还没有做(这块还得学新东西,坑还不少)。

    首先需要在gradle中添加Mockito依赖

dependencies {
    ...
    testCompile 'org.mockito:mockito-core:1.10.19'
    ...
}

    下面对上面的代码进行验证:

  @Test public void checkLoginPwd() throws Exception {
	//mock creation 模拟创建对象
	LoginPwdFragment loginPwdFragment = mock(LoginPwdFragment.class); 
	//作为构造参数传入
	LoginPwdPresenter mPresenter = new LoginPwdPresenter(null, loginPwdFragment); 
	//Action 该方法被调用时,模拟创建的对象也会被调用 
	mPresenter.checkLoginPwd("123456789");
	//Assert 
	//验证是否调用过一次 
	Mockito.verify(loginPwdFragment).gotoLoginPwdConfirmFragment(); 
	}    

    当checkLoginPwd()被调用的时候有,两个if语句条件都不满足,就会调用gotoLoginPwdConfirmFragment(),所以验证通过。如果传入的参数小于8位或者为空,验证就会失败。下面我们验证参数小于8位的情况:

  @Test public void checkLoginPwd() throws Exception {
   //mock creation 模拟创建对象
   LoginPwdFragment loginPwdFragment = mock(LoginPwdFragment.class);
  //作为构造参数传入
   LoginPwdPresenter mPresenter = new LoginPwdPresenter(null, loginPwdFragment);
    //Action   该方法被调用时,模拟创建的对象也会被调用
    mPresenter.checkLoginPwd("1234567");
    //Assert
   //验证是否调用过一次  
    Mockito.verify(loginPwdFragment).showText(Mockito.eq("登录密码必须大于8位数"));
  }
    上面的测试代码由于参数小于8位,走第二个if语句,我们就需要验证showText(参数)方法是否被调用,注意:showText()方法是有参数的,而且参数是固定的字符串,我们需要使用Mockito.eq()方法校验参数是否相同。如果参数是变量我们可以使用Mockito.any(),这样参数就不做比较,只验证showText(参数)是否被调用过。

    Mockito的api还需要去官网看:直通车

    我们也可以注解方式Mock创建模拟对象,但是需要在测试方法运行前初始化。代码如下:

public class LoginPwdPresenterTest {
  @Mock LoginPwdFragment loginPwdFragment;
  @Before public void setUp() throws Exception {
   //初始化
    MockitoAnnotations.initMocks(this);
  }
  @Test public void checkLoginPwd() throws Exception {
  //作为构造参数传入
   LoginPwdPresenter mPresenter = new LoginPwdPresenter(null, loginPwdFragment);
    //Action   该方法被调用时,模拟创建的对象也会被调用
    mPresenter.checkLoginPwd("1234567");
    //Assert
   //验证是否调用过一次  
    Mockito.verify(loginPwdFragment).showText(Mockito.eq("登录密码必须大于8位数"));
 }
 }
    有时候会碰到调用方法获取某个值,但是无法获取到,Android单元测试你懂得。例如下面的情况,领导要求代码中不能出现hardcode(硬编码)。所有hardcode都必须放在String文件里面,这时我们就需要通过Resource.getString(int stringId)来获取字符串,但是单元测试是获取不到的,返回null,验证通不过。这时我们就需要使用Mockito.when(T methodCall).thenReturn(T value)来指定返回值。代码如下:
@Mock LoginPwdFragment loginPwdFragment;
@Mock Resorce resource;
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test public void checkLoginPwd_EmptyLoginPwd_ShowEmptyErrorMessage() throws Exception {
// Arrange 当resource.getString(R.string.loginpwd_notnull)被调用时返回字符串"登录密码不能为空"
when(resource.getString(R.string.loginpwd_notnull)).thenReturn("登录密码不能为空");
mPresenter = new LoginPwdPresenter(null, loginPwdFragment);
//Action
mPresenter.checkLoginPwd("");
//Assert 
verify(loginPwdFragment).showText(eq("登录密码不能为空"));
}

还有一种情况是测试方法内调用带回调的方法单元测试,如下面代码:

 @Override public void remote(String loginpwd, String pwdType) {
    try {
      mView.showLoading();
      JSONObject jsonObject = new JSONObject();
      jsonObject.put(Constant.KEY_PWD, loginpwd);
      jsonObject.put(Constant.KEY_PWDTYPE, pwdType);
      remoteRepo.remotePost(Constant.testUrl.getURL_PWD(), jsonObject, ApplicationID.class, new RepoCallBack() {
        @Override public <T> void onSuccess(T response) {
          mView.hideLoading();
          mView.gotoSuccessBindFragment();
        }

        @Override public void onFailed(String response) {
          mView.hideLoading();
          mView.showToast(response);
        }

        @Override public void onError(String response) {
          mView.hideLoading();
          mView.showToast(response);
        }
      });
    } catch (JSONException e) {
      e.printStackTrace();
    }
  }
    上面的代码写单元测试就需要使用Mockito.doAnswer(Answer answer)方法模拟回调,测试方法如下:
    @Test
    public void remote_success_gotoSuccessBindFragment() throws Exception {
        // Arrange
        Mockito.doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                RepoCallBack repoCallBack = (RepoCallBack) invocation.getArguments()[3];// callback是第四个参数
                repoCallBack.onSuccess(new ApplicationID());
                return null;
            }
        }).when(remoteRepo)
          .remotePost(eq(Constant.testUrl.getURL_PWD()), Mockito.any(JSONObject.class), eq(ApplicationID.class),
                        Mockito.any(RepoCallBack.class));
        mPresenter = new LoginPwdPresenter(remoteRepo, loginPwdFragment);
        //Action
        mPresenter.remote("123123123123", "pwd");
        //Assest
        Mockito.verify(loginPwdFragment).gotoSuccessBindFragment();
        Mockito.verify(loginPwdFragment).hideLoading();
    }

    remoteRepo是mock创建的对象,当remoteRepo的remotePost()方法被调用时,会执行Answer的answer方法,通过InvocationOnMock可获取到参数列表,通过参数列表获取第四个回调参数RepoCallBack对象,通过RepoCallBack对象调用onSuccess方法模拟回调触发。所以当remote方法被调用时就会走onSuccess回调方法。onSuccess方法的参数是真实传递的。    

    Mokcito先写到这里,其他的方面我也还在摸索着学习。

    很荣幸我们能一起学习,一起成长。如果文中有错误,欢迎大神留言指正。谢谢!

---------------github源码地址---------------------    

    想要继续跟我一起学习一起成长,请关注我的公众号:程序员持续发展方案

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值