- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我想测试我的 Android View 模型。特别是当 setter 是否应该通知更改时。
View 模型如下所示(具有更多可绑定(bind)属性):
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);
}
}
在我的单元测试中,我注册了一个监听器,以获取通知并使用以下类跟踪它们:
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) 按照 https://fernandocejas.com/2014/04/08/unit-testing-asynchronous-methods-with-mockito/ 中的描述使用 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 答案中所述。
他们都没有帮助我。我该怎么做才能测试绑定(bind)通知?
最佳答案
您的测试作为示例项目中的本地单元测试工作(链接到 GitHub 存储库 here)。我无法重现您遇到的错误。包含导入的测试类的粗略工作示例如下 - 它生成 EditViewModel
的 100% 代码覆盖率:
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
中编写仪器测试 - 参见 this question为了区别。
此外,这种在 ViewModel
中具有复杂逻辑的场景通常可以通过提取辅助对象来更好地测试,使该对象成为构造函数内部传递的 ViewModel 的依赖项,并针对辅助对象进行测试,而不是而不是针对 ViewModel 本身。
这似乎有悖常理,因为我们习惯于将模型视为没有依赖关系的数据对象。但是,ViewModel 不仅仅是一个模型 - 通常它最终会负责将模型转换为 View 以及将 View 转换为模型,如讨论中所述 here .
假设您有一个经过测试的类 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);
}
}
关于通知的Android绑定(bind)和JUnit测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44684014/
我有一个 foo 类,它有一个 bar 方法,它接受可调用的东西(函数指针/仿函数)。这个可调用的东西应该作为绑定(bind)元素传递给另一个方法 doit 和第三个方法 bar_cb 方法。 #in
我正在尝试在我的 WPF 4.0 应用程序(使用 VS 2010 Pro RTM)中创建自定义 TabItem 模板/样式,但尽管一切似乎都正常工作,但我注意到跟踪窗口中存在绑定(bind)错误。 我
作为一名刚接触 Android 的开发人员,我想我可能误解了绑定(bind)服务。 我创建了一项服务来结束对服务器的访问。作为此服务的一部分,该服务正在监听多播地址,以识别本地网络上的设备何时出现和消
这个问题在这里已经有了答案: What is the use of the JavaScript 'bind' method? (23 个回答) 关闭 7 年前。 所以我一直在尝试了解一些 JS 上
我不明白这三种语法之间的区别: where a = f (b) do a <- f (b) do let a = f (b) 我确实明白了a <- f(b)与其他两个不同,在大多数情况下,我尝试了所有
我在将 Cocoa 项目从手动同步接口(interface)模型转换为绑定(bind)模型时遇到问题,这样我就不必担心接口(interface)粘合代码。 我关注了 CocoaDevCentral C
我正在尝试找出一种好的方法来对处理大数据集的代码进行并行化,然后将结果数据导入 RavenDb。 数据处理受 CPU 限制和数据库导入 IO 限制。 我正在寻找一种解决方案,以对 Environmen
我正在 foreach 循环中生成单选按钮。我试图将选中的属性绑定(bind)到父级中的基本可观察值。不幸的是,当单击单选按钮时,父级的属性似乎没有在单击处理程序中更新。 基于一些previous w
在我的 Windows Phone 应用程序中,我有两个 LongListSelectors并排在页面上。我想做到这一点,以便当用户滚动其中一个时,另一个滚动相同的量。 两个 LongListSele
我在网上看到这个问题准备面试: Given a non-preemptive kernel which type of process will get affected morein terms o
我有一个 foreach 绑定(bind),如下所示: Summary Permitting 原因是有两个选项卡始终存在,并且我根据是否添加了其他选项卡来添加其他选项
任何人都有绑定(bind)相同的情况DataContext到 TextBlock 中的 Text 属性(例如)。 我必须分配 DataContext以我的风格反射(reflect)基于 Datacon
给定以下代码: Login 和下面的javascript $(function () { $('#btnLogin').click(function () { co
我使用 boost::asio 创建了一个服务器。我在绑定(bind)到端点时遇到问题。所以,如果我在构造函数中初始化一个接受器: Server::Server(QWidget *parent) :
我正在将现有项目从 MySQL 转换为 Postgres。代码中有相当多的原始 SQL 文字使用 ? 作为占位符,例如 SELECT id FROM users WHERE
似乎在绑定(bind)某些数据时出错了,有人可以帮我解决我哪里出错了,尽管我无法弄清楚。 真的不需要在这里显示太多,这是 Binding,我已经通过移除背景并在其中放置颜色来测试背景,效果很好。 编辑
我正在尝试使用 wcf 构建一个 http 监听器(web 服务)。这个监听器是一个更大的桌面应用程序的一部分。此桌面应用程序还会调用 http 监听器。 当监听器接收到数据时,它应该被传递到桌面应用
嘿嘿。 我正在使用 Node.JS 和 child_process 来生成 bash 进程。我试图了解我是否正在执行 I/O 绑定(bind)、CPU 绑定(bind)或两者兼而有之。 我正在使用 p
尝试执行以下操作并出现“Got interpolation ({{}}) where expression was expected”错误。 {{item.name}} 谢谢!
我有一个导入的 Java 库,它是我解决方案中的“绑定(bind)库”项目。 我正在尝试从解决方案中的另一个项目绑定(bind)到第 3 方库中的服务。 第 3 方库文档 [在 java 中] 非常简
我是一名优秀的程序员,十分优秀!