Android MVP架构中Mockito测试Presenter类的问题求助
问题分析与解决方案
先帮你拆解下导致报错的核心问题,再给出完整的修正方案:
1. 测试输入触发了提前返回逻辑
当你调用signInPresenter.onSignInClicked("", "")时,Presenter里的ValidationUtil.checkEmail(email)会直接判定空邮箱无效,执行mSignInView.showErrorMessage(R.string.error_invalid_email)后就直接return了,根本不会走到mSignInView.showLoading()这一步,所以Mockito会提示你这个方法从未被调用。
2. showLoading方法签名不匹配
你的Presenter里调用的是无参的mSignInView.showLoading(),但测试代码里却在验证带字符串参数的showLoading("Loading"),方法签名完全不一致,这也是导致报错的关键原因。
修正后的完整测试方案
首先要先优化Presenter的依赖注入(原来直接new SignInInteractorImpl的写法无法mock,不利于测试),再针对不同场景编写测试用例:
第一步:优化Presenter的构造方法
修改SignInPresenterImpl的构造,支持传入SignInInteractor,方便测试时注入mock对象:
public class SignInPresenterImpl extends BasePresenterImpl implements SignInPresenter { private SignInView mSignInView; private SignInInteractor mSignInInteractor; // 给测试用的构造方法:注入依赖 public SignInPresenterImpl(@NonNull final SignInView signInView, @NonNull SignInInteractor signInInteractor) { mSignInView = signInView; mSignInInteractor = signInInteractor; } // 生产环境用的构造方法 public SignInPresenterImpl(@NonNull final SignInView signInView) { this(signInView, new SignInInteractorImpl()); } // 原有的onSignInClicked等方法保持不变 }
第二步:编写覆盖全场景的测试用例
public class SignInPresenterImplTest { @Mock private SignInView mSignInView; @Mock private SignInInteractor mSignInInteractor; private SignInPresenter signInPresenter; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // 传入mock的Interactor signInPresenter = new SignInPresenterImpl(mSignInView, mSignInInteractor); } // 测试:无效邮箱时显示错误,不触发加载 @Test public void whenInvalidEmail_shouldShowEmailError() { signInPresenter.onSignInClicked("", "validPass123"); verify(mSignInView).showErrorMessage(R.string.error_invalid_email); verify(mSignInView, never()).showLoading(); } // 测试:无效密码时显示错误,不触发加载 @Test public void whenInvalidPassword_shouldShowPasswordError() { signInPresenter.onSignInClicked("test@example.com", "123"); verify(mSignInView).showErrorMessage(R.string.error_invalid_password); verify(mSignInView, never()).showLoading(); } // 测试:有效凭证时显示加载,并调用Interactor @Test public void whenValidCredentials_shouldShowLoadingAndCallInteractor() { String validEmail = "test@example.com"; String validPassword = "ValidPass123"; signInPresenter.onSignInClicked(validEmail, validPassword); verify(mSignInView).showLoading(); verify(mSignInInteractor).login(eq(validEmail), eq(validPassword), any(BaseInteractor.ApiListener.class)); } // 测试:登录失败时隐藏加载并显示错误 @Test public void whenLoginFails_shouldHideLoadingAndShowError() { String validEmail = "test@example.com"; String validPassword = "ValidPass123"; // 触发登录,捕获传入的ApiListener signInPresenter.onSignInClicked(validEmail, validPassword); ArgumentCaptor<BaseInteractor.ApiListener> listenerCaptor = ArgumentCaptor.forClass(BaseInteractor.ApiListener.class); verify(mSignInInteractor).login(eq(validEmail), eq(validPassword), listenerCaptor.capture()); // 模拟登录失败回调 ApiError mockError = new ApiError(); mockError.setMessage("Invalid credentials"); listenerCaptor.getValue().onFailure(mockError, null); // 验证UI操作 verify(mSignInView).hideLoading(); verify(mSignInView).showErrorMessage("Invalid credentials"); } }
额外提示
- 尽量避免在Presenter、ViewModel这类逻辑类里直接
new依赖对象,通过构造注入的方式能让代码更易测试、更灵活。 - 测试时要针对每个分支(成功、失败、验证不通过等)编写单独的用例,确保所有逻辑都被覆盖到。
内容的提问来源于stack exchange,提问作者Rahul
相关产品推荐
相关产品推荐

