Привязка Android и проверка уведомления JUnit

Я хочу проверить свои модели Android. Особенно, когда сеттер должен уведомлять об изменениях или нет.

Модель вида выглядит так (с более связующими свойствами):

public class EditViewModel extends BaseObservable { private String _comment; @Bindable public String getComment() { return _comment; } public void setComment(String comment) { if (_comment == null && comment == null) { // No changes, both NULL return; } if (_comment != null && comment != null && _comment.equals(comment)) { //No changes, both equals return; } _comment = comment; // Notification of change notifyPropertyChanged(BR.comment); } } 

В моем UnitTest я регистрирую слушателя, чтобы получать уведомления и отслеживать их следующим классом:

 public class TestCounter { private int _counter = 0; private int _fieldId = -1; public void increment(){ _counter++; } public int getCounter(){ return _counter; } public void setFieldId(int fieldId){ _fieldId = fieldId; } public int getFieldId(){ return _fieldId; } } 

Поэтому мои методы тестирования выглядят следующим образом:

 @Test public void setComment_RaisePropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment("One"); final TestCounter pauseCounter = new TestCounter(); // -- Change listener sut.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { pauseCounter.increment(); pauseCounter.setFieldId(propertyId); } }); String newComment = "two"; // Act sut.setComment(newComment); // Assert assertThat(pauseCounter.getCounter(), is(1)); assertThat(pauseCounter.getFieldId(), is(BR.comment)); assertThat(sut.getComment(), is(newComment)); } 

Если я выполняю только методы тестирования, этот подход работает хорошо. Если я выполняю все тесты на одном, некоторые сбой, то уведомление было вызвано в 0 раз. Я думаю, что эти утверждения вызываются до того, как обратный вызов может быть обработан.

Я уже пробовал:

(1) Обманите слушателя с помощью mockito, как описано в https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/ .

 @Test public void setComment_RaisePropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment("One"); Observable.OnPropertyChangedCallback listener = mock(Observable.OnPropertyChangedCallback.class); // -- Change listener sut.addOnPropertyChangedCallback(listener); String newComment = "two"; // Act sut.setComment(newComment); // Assert verify(listener, timeout(500).times(1)).onPropertyChanged(any(Observable.class), anyInt()); } 

(2) Пытался использовать CountDownLatch как описано в нескольких ответах SO.

Никто из них не помог мне. Что я могу сделать, чтобы проверить уведомление о привязке?

Ваши тесты работают как локальные тесты в примерном проекте (ссылка на GitHub repo здесь ). Я не могу воспроизвести ошибки, которые вы получаете. Рабочий пример тестового класса с импортом выглядит следующим образом: он генерирует 100% -ный охват кода вашего EditViewModel :

 import android.databinding.Observable; import org.junit.Test; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; public class EditViewModelTest { @Test public void setNewNonNullCommentRaisesPropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment("One"); Observable.OnPropertyChangedCallback listener = mock(Observable.OnPropertyChangedCallback.class); sut.addOnPropertyChangedCallback(listener); String newComment = "two"; // Act sut.setComment(newComment); // Assert verify(listener).onPropertyChanged(sut, BR.comment); } @Test public void setNewNullCommentRaisesPropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment("One"); Observable.OnPropertyChangedCallback listener = mock(Observable.OnPropertyChangedCallback.class); sut.addOnPropertyChangedCallback(listener); String newComment = null; // Act sut.setComment(newComment); // Assert verify(listener).onPropertyChanged(sut, BR.comment); } @Test public void setEqualCommentDoesntRaisePropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment("One"); Observable.OnPropertyChangedCallback listener = mock(Observable.OnPropertyChangedCallback.class); sut.addOnPropertyChangedCallback(listener); String newComment = "One"; // Act sut.setComment(newComment); // Assert verify(listener, never()).onPropertyChanged(sut, BR.comment); } @Test public void setNullToNullDoesntRaisePropertyChange() { // Arrange EditViewModel sut = new EditViewModel(null); sut.setComment(null); Observable.OnPropertyChangedCallback listener = mock(Observable.OnPropertyChangedCallback.class); sut.addOnPropertyChangedCallback(listener); String newComment = null; // Act sut.setComment(newComment); // Assert verify(listener, never()).onPropertyChanged(sut, BR.comment); } } 

Чтобы диагностировать возникшую у вас проблему, убедитесь, что у вас есть правильные зависимости, как показано ниже:

 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile "org.mockito:mockito-core:+" androidTestCompile "org.mockito:mockito-android:+" } 

Более старые настройки с mockito и dexmaker более недействительны. Самые последние версии Mockito можно найти на Maven Central

Также, пожалуйста, проверьте, что вы пишете тест локального блока в test а не инструментальный тест в androidTest – см. Этот вопрос для разницы.

Кроме того, такие сценарии со сложной логикой внутри ViewModel часто лучше проверяются путем извлечения вспомогательного объекта, что делает объект зависимым для ViewModel, переданного внутри конструктора, и тестирует его вместо вспомогательного объекта, а не против самого ViewModel.

Это может показаться противоречивым, потому что нас обучают думать о моделях как объектах данных без зависимостей. Тем не менее, ViewModel – это больше, чем простая модель – часто она начинает брать на себя ответственность за преобразование обеих моделей для просмотра и просмотра модели, как в обсуждении здесь .

Предположим, у вас есть проверенный класс MyDateFormat с единичным тестом против него где-то еще в вашем проекте. Теперь вы можете написать ViewModel, который зависит от него следующим образом:

 public class UserViewModel extends BaseObservable { private final MyDateFormat myDateFormat; @Nullable private String name; @Nullable private Date birthDate; public ProfileViewModel(@NonNull MyDateFormat myDateFormat) { this.myDateFormat = myDateFormat; } @Bindable @Nullable public String getName() { return name; } @Bindable @Nullable public String getBirthDate() { return birthDate == null ? null : myDateFormat.format(birthDate.toDate()); } public void setName(@Nullable String name) { this.name = name; notifyPropertyChanged(BR.name); } public void setBirthDate(@Nullable Date birthDate) { this.birthDate = birthDate; notifyPropertyChanged(BR.birthDate); } }