使用mockito和事件监听器进行unit testingMVP

Android Studio 2.1.2 

我想testingLoginModelImp中onUsernameError,onPasswordError和onSuccess的callback是否被实际调用。 我不知道如何testing事件监听器。 但是,testing失败,因为这些function从来没有被调用。 我正在用mockito嘲笑他们,并试图validation他们。

这是我的代码到目前为止。

演示者界面

 public interface LoginPresenterContract<LoginFragmentViewContract> { void validateCredentials(); void attachView(LoginFragmentViewContract view); void detachView(); } 

演示者实施

 public class LoginPresenterImp implements LoginPresenterContract<LoginFragmentViewContract>, LoginModelContract.OnLoginCompletedListener { private LoginModelContract mLoginModelContract; private LoginFragmentViewContract mLoginFragmentView; public LoginPresenterImp(LoginModelContract loginModelContract) { mLoginModelContract = loginModelContract; } /* * LoginPresenterContact - implementation */ @Override public void attachView(LoginFragmentViewContract view) { mLoginFragmentView = view; } @Override public void detachView() { mLoginFragmentView = null; } @Override public void validateCredentials() { if(mLoginModelContract != null) { mLoginModelContract.login( mLoginFragmentView.getUsername(), mLoginFragmentView.getPassword(), LoginPresenterImp.this); } } /* * LoginModelContract.OnLoginCompletedListener - implementation */ @Override public void onUsernameError() { if(mLoginFragmentView != null) { mLoginFragmentView.onLoginFailed("Incorrect username"); } } @Override public void onPasswordError() { if(mLoginFragmentView != null) { mLoginFragmentView.onLoginFailed("Incorrect password"); } } @Override public void onSuccess() { if(mLoginFragmentView != null) { mLoginFragmentView.onLoginSuccess(); } } } 

模型界面

 public interface LoginModelContract { interface OnLoginCompletedListener { void onUsernameError(); void onPasswordError(); void onSuccess(); } void login(String username, String password, OnLoginCompletedListener onLoginCompletedListener); } 

模型实现

 public class LoginModelImp implements LoginModelContract { /* Testing Valid username and passwords */ private static String validUsername = "steve"; private static String validPassword = "1234"; @Override public void login(final String username, final String password, final OnLoginCompletedListener onLoginCompletedListener) { boolean hasSuccess = true; if(TextUtils.isEmpty(username) || !username.equals(validUsername)) { /* TEST onUsernameError() */ onLoginCompletedListener.onUsernameError(); hasSuccess = false; } if(TextUtils.isEmpty(password) || !password.equals(validPassword)) { /* TEST onPasswordError() */ onLoginCompletedListener.onPasswordError(); hasSuccess = false; } if(hasSuccess) { /* TEST onSuccess() */ onLoginCompletedListener.onSuccess(); } } } 

用Mockito进行JUnit4testing

 public class LoginPresenterImpTest { private LoginFragmentViewContract mMockViewContract; private LoginModelContract mMockModelContract; private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; private LoginPresenterContract<LoginFragmentViewContract> mLoginPresenterContract; @Before public void setUp() throws Exception { mMockViewContract = Mockito.mock(LoginFragmentViewContract.class); mMockModelContract = Mockito.mock(LoginModelContract.class); mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); mLoginPresenterContract = new LoginPresenterImp(mMockModelContract); mLoginPresenterContract.attachView(mMockViewContract); } @Test public void shouldSuccessWithValidCredentials() { when(mMockViewContract.getUsername()).thenReturn("steve"); when(mMockViewContract.getPassword()).thenReturn("1234"); mLoginPresenterContract.validateCredentials(); verify(mMockViewContract, times(1)).getUsername(); verify(mMockViewContract, times(1)).getPassword(); verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); verify(mMockOnLoginCompletedListener, never()).onPasswordError(); verify(mMockOnLoginCompletedListener, never()).onUsernameError(); } } 

有什么办法来testing这个实现吗?

非常感谢您的任何build议,

  • Android RxJava 2 JUnit测试 - android.os.Looper中的getMainLooper没有模拟RuntimeException
  • 如何使用mock()和spy()来testing静态方法
  • 从命令行运行androidunit testing?
  • 由于IllegalStateexception,Android TestRunner失败
  • Espresso自定义ViewMatcher不匹配描述不会出现在日志中
  • ActivityTestRule - 如何在Application的onCreate之前调用代码
  • Android,Gradle。 如何构build应用程序并从testing应用程序运行testing
  • 访问androidtesting项目中的资源
  • testing类LoginPresenterImpTest是关于LoginPresenterImp类的testing,它应该只使用它的实际实现和合作者的LoginPresenterImpLoginModelContract.OnLoginCompletedListener类是LoginModelContract.OnLoginCompletedListener的一个合作者,所以在一个精心devise的,纯粹的LoginPresenterImpunit testing中,像你一样,从来没有被调用是完全正常的。 我build议的解决scheme是分别testingLoginModelImp:

     public class LoginModelImpTest { private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; private LoginModelImp loginModelImp; @Before public void setUp() throws Exception { mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); loginModelImp = new LoginModelImp(); } @Test public void shouldSuccessWithValidCredentials() { loginModelImp.login("steve", "1234", mMockOnLoginCompletedListener);; verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); verify(mMockOnLoginCompletedListener, never()).onPasswordError(); verify(mMockOnLoginCompletedListener, never()).onUsernameError(); } } 

    或者,您必须在您的LoginPresenterImpTest使用LoginModelImp的实际实现,并在侦听器(即演示者本身)上监听,或configuration模拟以使其调用侦听器。 这是一个例子,但我不会使用这个:

     public class LoginPresenterImpTest { private LoginFragmentViewContract mMockViewContract; private LoginModelContract mModelContract; private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; private LoginPresenterContract<LoginFragmentViewContract> mLoginPresenterContract; @Before public void setUp() throws Exception { mMockViewContract = Mockito.mock(LoginFragmentViewContract.class); mModelContract = new LoginModelImp(); LoginPresenterImp spyPresenterImp = Mockito.spy(new LoginPresenterImp(mModelContract)); mLoginPresenterContract = spyPresenterImp; mMockOnLoginCompletedListener = spyPresenterImp; mLoginPresenterContract.attachView(mMockViewContract); } @Test public void shouldSuccessWithValidCredentials() { when(mMockViewContract.getUsername()).thenReturn("steve"); when(mMockViewContract.getPassword()).thenReturn("1234"); mLoginPresenterContract.validateCredentials(); verify(mMockViewContract, times(1)).getUsername(); verify(mMockViewContract, times(1)).getPassword(); verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); verify(mMockOnLoginCompletedListener, never()).onPasswordError(); verify(mMockOnLoginCompletedListener, never()).onUsernameError(); } } 

    这归结为用户故事和用例之间的区别。 在这种情况下,你有1个用户故事(例如“作为用户,我想login,所以我提供我的用户名和密码”),但实际上至less有3个用例:正确的用户名/密码,错误的密码,错误的用户名/正确的密码等等。作为一般的最佳实践,您希望testing与用例1:1对应,所以我会推荐如下所示:

      @Test public void shouldCompleteWithValidCredentials() { mMockModelContract.login("steve", "1234", mMockOnLoginCompletedListener); verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); } @Test public void shouldNotCompleteWithInvalidUser() { mMockModelContract.login("wrong_user", "1234", mMockOnLoginCompletedListener); verify(mMockOnLoginCompletedListener, times(1)).onUsernameError(); } @Test public void shouldNotCompleteWithInvalidPassword() { mMockModelContract.login("steve", "wrong_password", mMockOnLoginCompletedListener); verify(mMockOnLoginCompletedListener, times(1)).onPasswordError(); } 

    换句话说,对于testing1,您试图肯定validation,当用户名和密码完成时,调用成功。 对于testing2,您正在validation调用onUsernameError的条件以及3,onPasswordError的条件。 所有这三个都是有效的事情要testing,你是正确的,要validation他们被称为,但你需要把他们作为不同的用例。

    为了完整性,我将validationWrong_User / Wrong_Password发生了什么,并validation如果发生了N次Wrong_Password条件会发生什么(您是否需要阻止帐户?)。

    希望这可以帮助。 祝你好运。

    我认为,因为你在嘲笑LoginModelContractOnLoginCompletedListenerOnLoginCompletedListener你不能断言onUsernameErroronPasswordErroronSuccess实际上是被调用的,因为通过onPasswordError LoginModelContract ,“真正的”login方法(应该调用这些方法)不会被执行,而只是被模仿的方法会被调用。 你可以用这样的方法触发这些方法:

     Mockito.doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); OnLoginCompletedListener listener = (OnLoginCompletedListener) args[2]; listener.onUsernameError(); return null; } }).when(mMockModelContract).login(anyString(), anyString(), any(OnLoginCompletedListener.class)).thenAnswer(); 

    但是,这样的testing是没有意义的,因为你正在明确地调用你正在testing的东西。

    在我看来,仅仅在没有LoginFragmentViewContractLoginPresenterContract情况下testingLoginModelContract会更有意义。 就像是:

     public class LoginPresenterImpTest { private LoginModelContract mMockModelContract; private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; @Before public void setUp() throws Exception { mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); mMockModelContract = new LoginModelContract(); } @Test public void shouldSuccessWithValidCredentials() { mMockModelContract.login("steve", "1234", mMockOnLoginCompletedListener); verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); verify(mMockOnLoginCompletedListener, never()).onPasswordError(); verify(mMockOnLoginCompletedListener, never()).onUsernameError(); } } 

    我可能会错过你的观点,但你有没有尝试过使用PowerMock?

    您将需要以下依赖项:

    • testCompile“org.powermock:powermock-module-junit4:1.6.5”
    • testCompile“org.powermock:powermock-module-junit4-rule:1.6.5”
    • testCompile“org.powermock:powermock-api-mockito:1.6.5”
    • testCompile“org.powermock:powermock-classloading-xstream:1.6.5”

    然后用这个方法:

     @PowerMockIgnore({ "org.mockito.*", "android.*" }) @PrepareForTest(DownloadPresenterContract.Events.class) public class DownloadModelTest { @Rule public PowerMockRule rule = new PowerMockRule(); private DownloadPresenterContract.Events mockEvents; @Before public void setUp() throws Exception { this.mockEvents = PowerMockito.spy(new DownloadPresenterContract.Events()); PowerMockito.whenNew(DownloadPresenterContract.Events.class) .withNoArguments() .thenReturn(this.mockEvents); } @Test public void testStaticMocking() { //Do your logic, which should trigger mockEvents actions Mockito.verify(this.mockEvents, Mockito.times(1)).onDownloadSuccess(); //Or use this: //PowerMockito.verifyPrivate(this.mockEvents, times(1)).invoke("onDownloadSuccess", "someParam"); } 

    }