Android RecyclerView 适配器项目计数在单元测试中返回 0
Posted
技术标签:
【中文标题】Android RecyclerView 适配器项目计数在单元测试中返回 0【英文标题】:Android RecyclerView Adapter Item count is returning 0 on unit testing 【发布时间】:2017-10-09 10:55:33 【问题描述】:我正在尝试使用 androidJunit4 测试 RecyclerView,这是我的测试代码:
@Rule
public ActivityTestRule<ProductListActivity> rule = new ActivityTestRule<>(ProductListActivity.class);
............................
..........................
@Test
public void ensureDataIsLoadingOnSuccess() throws Exception
ProductListActivity activity = rule.getActivity();
...........................
............
activity.runOnUiThread(new Runnable()
public void run()
activity.displayProducts(asList(product1, product2), 0);
);
assertEquals(2, mAdapter.getItemCount());
assertThat(((ProductAdapter) mAdapter).getItemAtPosition(0),sameInstance(product1));
assertThat(((ProductAdapter) mAdapter).getItemAtPosition(1),sameInstance(product2));
这是我在 Activity 中的 displayProducts() 代码:
@Override
public void displayProducts(List<Product> products, Integer pageNo)
progressBar.setVisibility(View.GONE);
if (pageNo == 0 && products.size() == 0)
noProductTextView.setVisibility(View.VISIBLE);
else
mProductAdapter.addProduct(products);
noProductTextView.setVisibility(View.GONE);
productListView.setVisibility(View.VISIBLE);
它给出的错误如下:
junit.framework.AssertionFailedError: expected:<2> but was:<0>
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.failNotEquals(Assert.java:287)
at junit.framework.Assert.assertEquals(Assert.java:67)
at junit.framework.Assert.assertEquals(Assert.java:199)
at junit.framework.Assert.assertEquals(Assert.java:205)
at com.kaushik.myredmart.ui.ProductListActivityTest.ensureDataIsLoadingOnSuccess(ProductListActivityTest.java:94)
请帮忙看看我的代码有什么问题?
【问题讨论】:
如何设置mAdapter
?
动画是你在 ui 测试中的敌人,每个进度条、自定义动画或在非异步线程池上完成的任何工作都不会在 espresso 中注册,它只会通过其他断言运行,因为它认为没有什么可以等待的。还尝试编写您注入的测试并在 setup 和 teardown 中设置数据,然后您的活动将在构建期间拥有它。你有没有在 displayProducts() 之后调用 notifydataset 改变?
mAdapter 从何而来?我认为您正在检查错误的适配器
【参考方案1】:
原因是您的 Espresso 测试没有等待您的加载任务,这很耗时。
您需要使用espresso-idling-resource
告诉它等待此任务完成。
然后你需要一个类来实现IdlingResource
并将它声明为你的Activity。
当您的 Espresso 测试运行时,它会知道并等待您的耗时任务完成并测试结果。
首先,添加它的依赖。
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.2"
其次,您需要文件夹 src/main/java/your-package 中的两个 Java 文件。 SimpleCountingIdlingResource.java
public final class SimpleCountingIdlingResource implements IdlingResource
private final String mResourceName;
private final AtomicInteger counter = new AtomicInteger(0);
// written from main thread, read from any thread.
private volatile ResourceCallback resourceCallback;
/**
* Creates a SimpleCountingIdlingResource
*
* @param resourceName the resource name this resource should report to Espresso.
*/
public SimpleCountingIdlingResource(String resourceName)
mResourceName = checkNotNull(resourceName);
@Override public String getName()
return mResourceName;
@Override public boolean isIdleNow()
return counter.get() == 0;
@Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback)
this.resourceCallback = resourceCallback;
/**
* Increments the count of in-flight transactions to the resource being monitored.
*/
public void increment()
counter.getAndIncrement();
/**
* Decrements the count of in-flight transactions to the resource being monitored.
*
* If this operation results in the counter falling below 0 - an exception is raised.
*
* @throws IllegalStateException if the counter is below 0.
*/
public void decrement()
int counterVal = counter.decrementAndGet();
if (counterVal == 0)
// we've gone from non-zero to zero. That means we're idle now! Tell espresso.
if (null != resourceCallback)
resourceCallback.onTransitionToIdle();
if (counterVal < 0)
throw new IllegalArgumentException("Counter has been corrupted!");
EspressoIdlingResource.java
public class EspressoIdlingResource
private static final String RESOURCE = "GLOBAL";
private static SimpleCountingIdlingResource mCountingIdlingResource =
new SimpleCountingIdlingResource(RESOURCE);
public static void increment()
mCountingIdlingResource.increment();
public static void decrement()
mCountingIdlingResource.decrement();
public static IdlingResource getIdlingResource()
return mCountingIdlingResource;
好的。让我们转到您有一项耗时任务的活动。 首先,将此方法放在底部。
@VisibleForTesting
public IdlingResource getCountingIdlingResource()
return EspressoIdlingResource.getIdlingResource();
在您耗时的任务中。你应该告诉你的 Espresso 像这样等待。
EspressoIdlingResource.increment();
yourTask.run(new Callback()
void onFinish()
EspressoIdlingResource.decrement();
)
最后一步是在您的 UI 测试类中定义这些方法。
@Before
public void registerIdlingResource()
Espresso.registerIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
/**
* Unregisters your idling resource so it can be garbage collected and does not leak any memory
*/
@After
public void unregisterIdlingResource()
Espresso.unregisterIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
是的。终于完成了。
【讨论】:
【参考方案2】:我可以在这里看到一个问题,您在 Main/UI 线程能够更新它之前查询列表大小。因此,您必须在当前线程中等待,直到 Activity 完成更新主线程上的列表。
你可以的,
Thread.sleep(500);
在Test类中等待,在Activity中测试list设置行为,你会发现断言是有效的。
由于主线程无限运行直到应用程序运行,您必须实现 Activity 提供的回调接口,以便在填充列表完成时收到通知。
【讨论】:
问题是为什么 Espresso 不等到MessageQueue
为空?
@azizbekian 因为它在另一个线程(后台)上运行。添加一个观察者设计模式并在它发生的确切时间捕捉它,否则使用线程睡眠方法。
您的澄清不正确:Espresso 在后台线程上运行,但是它会监听 UI 线程的 MessageQueue
是否为空,并且一旦为空只有在那之后,Espresso 才会继续使用匹配器。您建议解决问题的方法,但没有说明问题发生的原因。
那么什么是 actula 原因,什么是适当的解决方法?@azizbekian
我无法给出令人满意的说明,这就是我没有发布答案的原因。从您提供的代码来看,这应该可以工作,因为 Espresso 应该知道这些更改,并且应该等到所有来自 MessageQueue
的消息都被消费完。以上是关于Android RecyclerView 适配器项目计数在单元测试中返回 0的主要内容,如果未能解决你的问题,请参考以下文章
在我的 recyclerview 适配器类中实现 Android 底页
如何在 recyclerview 适配器中将动态视图添加到 android 布局
Android进阶之通用RecyclerView适配器打造方法
Android进阶之通用RecyclerView适配器打造方法