上篇已经介绍了单元测试,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源码地址---------------------
想要继续跟我一起学习一起成长,请关注我的公众号:程序员持续发展方案