如何使用JUnit或Mockito对RecyclerView的项点击进行单元测试

14

我目前正在尝试使用junit或mockito对recyclerview的addonitemclick listener进行单元测试。这是我的代码:

我目前正在尝试使用junit或mockito对recyclerview的addonitemclick listener进行单元测试。这是我的代码:

private void mypicadapter(TreeMap<Integer, List<Photos>> photosMap) {
    List<PhotoListItem> mItems = new ArrayList<>();

    for (Integer albumId : photosMap.keySet()) {
        ListHeader header = new ListHeader();
        header.setAlbumId(albumId);
        mItems.add(header);
        for (Photos photo : photosMap.get(albumId)) {
            mItems.add(photo);
        }


        pAdapter = new PhotoViewerListAdapter(MainActivity.this, mItems);
        mRecyclerView.setAdapter(pAdapter);
        //  set 5 photos per row if List item type --> header , else fill row with header.
        GridLayoutManager layoutManager = new GridLayoutManager(this, 5);
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                if (mRecyclerView.getAdapter().getItemViewType(position) == PhotoListItem.HEADER_TYPE)
                    // return the number of columns so the group header takes a whole row
                    return 5;
                // normal child item takes up 1 cell
                return 1;
            }
        });
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.addOnItemTouchListener(new PhotoItemClickListener(MainActivity.this,
                new PhotoItemClickListener.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, int position) {
                        if (pAdapter.getItemViewType(position) == PhotoListItem.HEADER_TYPE) return;

                        Photos photo = pAdapter.getItem(position);
                        Intent intent = new Intent(MainActivity.this, DetailViewActivity.class);
                        intent.putExtra(PHOTO_DETAILS, photo);
                        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                                MainActivity.this,

                                new Pair<>(view.findViewById(R.id.photoItem),
                                        getString(R.string.transition_name_photo))
                        );
                        ActivityCompat.startActivity(MainActivity.this, intent, options.toBundle());
                    }
                }));
    }

有没有办法对addOnItemTouchListener或OnItemClickListener/onitemclick进行单元测试,模拟功能等。我对单元测试很陌生,已经在网上查找了几个教程,但仍感到困惑。有关测试功能的逐步教程或任何建议都可以帮助我。此外,此函数中可能存在的任何其他可单元测试的情况也将有所帮助。谢谢!


1
绝对不是我在帮你,但我认为你想要做的不是单元测试,而是 UI 测试,在那里你可以使用 Espresso - mt0s
1
@JusticeBauer请尽量以积极的心态阅读所有评论。从几个单词中猜测某人的态度很难,而误解意图则很容易。 - njzk2
@Justice Bauer请查看此链接http://stackoverflow.com/questions/41033279/moving-a-recyclerview-via-touch-or-gesture-recognition/41122132#41122132。 请告诉我您的意见。 - Ronak Gadhia
2个回答

9
在单元测试中,拥有小而可测试的代码块非常重要,我宁愿有10个只负责单一功能的方法,而不是一个涵盖所有操作的方法。
所有使用的输入都应作为参数传递给方法,然后您可以测试在给定输入时是否会得到预期输出。
不要测试您没有掌控的内容——测试View的onClick()是AOSP工作的一部分。您可以测试如何响应onClickListener。
您应该拥有一个处理逻辑的测试类。然后,在您的测试中,实例化这个类以进行测试,并模拟其他所有内容(通常最好通过构造函数传递依赖项)。
例如:如果您有像下面这样的方法:
goToDetailActivity(Photo photo){...}

我建议您将其封装在接口中,我们称之为View。在View中,您还可以放置所有其他必须调用且与视图相关的方法,例如与视图组件交互、导航等。 然后,您应该拥有逻辑类,我们称之为Presenter:
public class Presenter {
Presenter(View:view) {
    this.view = view;
}

public void onPhotoClicked(Photo:photo) {
    if (shouldDetailScreenBeOpened())
        view.goToDetailActivity(Photo photo);
    else view.showError("error");
}

private boolean shouldDetailScreenBeOpened() {
    // do caclualtions here
    ...}
}

我将适配器视为视图的一部分,因此它没有真正的逻辑。所以要将点击传递给Presenter,你应该通过活动/片段(View实现)将其传递给Presenter(如果有人喜欢RxJava,可以使用RxBinding库)并调用它的onPhotoClicked(photo)方法。

在测试中,您需要模拟所需的事物(并且不是测试主体):

 View view= Mockito.mock(View.class);

 Presenter tested = Presenter(view); 

 Photo validPhoto = Mockitio.mock(Photo.class);
 Mockito.when(validPhoto.getUrl()).thanReturn("image.com")

 //call method which will be triggered on item click
 tested.onPhotoClicked(validPhoto)

 //Check if method was invoked with our object
 Mockito.verify(view).goToDetailActivity(validPhoto);

 //Check also not so happy path
 Photo invalidPhoto = Mockitio.mock(Photo.class);
 Mockito.when(invalidPhoto.getUrl()).thanReturn(null)

 //call method which will be triggered on item click
 tested.onPhotoClicked(invalidPhoto)
 Mockito.verify(view,never()).goToDetailActivity(invalidPhoto);
 Mockito.verify(view).showError("error")

一个不错的起点是vogella mokcito教程


1
这实际上帮助我朝着正确的方向前进,谢谢! - user2386226
我从类似的情况到达了这个SO,但是在调用getChildAt时,我不得不模拟并返回一个模拟的View。问题在于,当您调用performClick时,ViewHolder(在我的情况下至少)附加了一个OnClickListener,但它不会接收到单击事件,而模拟的那个会接收到。因此,无法执行ViewHolder中的代码。这至少是我的理解。也许@MarissaNicholas比我取得了更多的进展。 - jonalmeida
代码片段无效,我会在有时间时进行修复。因此应该有一些经过测试而不是模拟的代码。在你的例子中,你可以将你的点击事件传递给Presenter,这样就可以进行单元测试了。 - Wiktor Wardzichowski

4
我建议将您在 addOnItemTouchListener 中创建的匿名内部类提取为单独的类。
然后,我会编写有关 onItemClick 方法的相关(单元)测试。
然而,这非常依赖于您的应用程序的整体上下文以及您想要测试的确切内容。
有关单元测试与集成测试的讨论非常广泛,而且人们对什么才是真正的单元测试也存在一些困惑和不同意见。
我建议您从 Martin Fowler 的系列文章开始阅读更多关于这个主题的内容 - 例如:https://martinfowler.com/bliki/UnitTest.html 还有另一篇关于测试替身的文章,应该会指导您是否要使用模拟或存根:https://martinfowler.com/articles/mocksArentStubs.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接