使用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议,

Solutions Collecting From Web of "使用mockito和事件监听器进行unit testingMVP"

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"); } 

}