Модульное тестирование MVP с использованием mockito со слушателями событий
Android Studio 2.1.2
Я хочу проверить, что обратные вызовы onUsernameError, onPasswordError и onSuccess в LoginModelImp фактически вызываются. Я не уверен, как проверить прослушиватели событий. Однако тест не выполняется, поскольку эти функции никогда не вызываются. Я издеваюсь над ними с мокито и пытаюсь их проверить.
это мой код до сих пор.
ведущий интерфейс
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();
}
}
}
JUnit4 тест с Mockito
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();
}
}
есть ли способ проверить эту реализацию?
большое спасибо за любые предложения,
4 ответов
тест класс LoginPresenterImpTest
о тест LoginPresenterImp
класс, и он должен использовать только его фактическую реализацию и насмешки своих сотрудников. Класс!--4--> является сотрудником LoginModelImp
, поэтому в хорошо спроектированном и чистом юнит-тесте LoginPresenterImp
, как и ваш, совершенно нормально, что он никогда не называется.
Решение я предлагаю для тестирования LoginModelImp отдельно:
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();
}
}
кроме того, вы должны использовать фактическое осуществление LoginModelImp
в своем LoginPresenterImpTest
и шпионить за своим слушателем (то есть за самим ведущим) или настраивать насмешки, чтобы заставить их вызвать слушателя. Вот пример, но я бы не стал использовать этот:
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 пользовательская история (например, "как пользователь, я хочу войти, так что я предоставить свой логин и пароль"), но на самом деле существует как минимум 3 Варианты использования: правильный логин/пароль, логин правильный/неправильный пароль, неверный логин/пароль, и т. д. В качестве общей передовой практики вы хотите, чтобы тесты соответствовали 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();
}
In другими словами, Для теста 1 вы пытаетесь положительно проверить, что после завершения имени пользователя и пароля вызывается успех. Для теста 2 вы проверяете условия для вызова onUsernameError, а для 3-условия для onPasswordError. Все три являются допустимыми вещами для тестирования, и вы правы, что хотите проверить, что они называются, но вам нужно рассматривать их как разные варианты использования.
для полноты я бы проверил, что происходит на Wrong_User/Wrong_Password, а также проверьте, что происходит, если есть условие Wrong_Password N раз (вам нужна блокировка учетной записи?).
надеюсь, что это помогает. Удача.
Я думаю, потому что вы издеваетесь над LoginModelContract
и OnLoginCompletedListener
вы не можете утверждать, что onUsernameError
, onPasswordError
и onSuccess
на самом деле называются, потому что, издеваясь LoginModelContract
" реальный " метод входа в систему (который должен вызывать эти методы) не будет выполнен, но будет вызван только издевательский метод.
Вы можете запустить эти методы с чем-то вроде:
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();
но причина такого теста не имеет смысла, потому что вы явно вызываете то, что вы пытаетесь тест.
на мой взгляд, было бы разумнее просто проверить LoginModelContract
без LoginFragmentViewContract
и LoginPresenterContract
.
Что-то вроде:
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-модуль-junit4: 1.6.5"
- testCompile " org.powermock: powermock-модуль-junit4-правило: 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");
}
}