androidandroid-recyclerviewmockingandroid-testingandroid-junit

How do I unit test (with JUnit or mockito) recyclerview item clicks


I am currently trying to unit test recyclerview addonitemclick listner, with either junit or mockito. here's my code:

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());
                    }
                }));
    }

Is there a way I can unit test : addOnItemTouchListener or OnItemClickListener/onitemclick ,mock the functionality etc. I am pretty new to unit testing and been looking up online at a couple of tutorials and pretty confused. Any step by step tutorial for testing functions or any suggestions would help.Also, any other possible unit testable scenarios in this function would be helpful. Thanks!


Solution

  • In unit tests it's improtant to have small, testable chunks of code, I would rather have 10 methods with single resposinbility than one method for all actions.

    All used inputs should be delivered as parameters to method, than you test if at given input you will receive expected output.

    Don't test what you don't own - testing of View's onClick() is part of AOSP job. You can test how you react to onClickListener.

    You should have class under test that handles the logic. Than in your test you instantiate this class to test it and mock everything else (usually good way to go is to pass dependencies through constructor)

    Example:

    So that way if you have method like

    goToDetailActivity(Photo photo){...}
    

    I would wrap it in interface, lets call it View. In View you put also all other methods that your logic must call and are view related like interacting with view components, navigation etc. Than you should have your logic class, lets call it 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
        ...}
    }
    

    I treat my adapters as part of view, so it has no real logic. So to pass clicks to Presenter you should pass it through activity/fragment (View implementation) to Presenter (if someone is fun of RxJava, RxBinding library can be used) and call it's onPhotoClicked(photo) method.

    And in testing you have to mock things that you need (and are not subjects to test):

     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")
    

    Good staring point vogella mokcito tutorial.