Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing a Fragment class in isolation using Mockito

Added @VisibleForTesting and protected. My test can now this method:

   @VisibleForTesting
    protected void setupDataBinding(List<Recipe> recipeList) {
        recipeAdapter = new RecipeAdapter(recipeList);
        RecyclerView.LayoutManager layoutManager
                = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        rvRecipeList.setLayoutManager(layoutManager);
        rvRecipeList.setAdapter(recipeAdapter);
    }

Updated the test case using spy object: However, the real setupDataBinding(recipe) is getting called even when I created a mock of he spy that will get called. Maybe I am doing this wrong.

@Test
public void testShouldGetAllRecipes() {
    RecipeListView spy = Mockito.spy(fragment);
    doNothing().when(spy).setupDataBinding(recipe);

    fragment.displayRecipeData(recipe);

    verify(recipeItemClickListener, times(1)).onRecipeItemClick();
}

I am trying to test the methods in my Fragment class as below. However, I am trying to mock out the methods to verify that the methods are called the correct number of times. However, the problem is I have a private method setupDataBinding(...) that setups on the RecyclerView that is called from displayRecipeData(...). I want to mock these calls as I don't want to call the real object on the RecyclerView. I just want to verify that setupDataBinding(...) gets called.

I have tried using spy and VisibleForTesting, but still not sure how to do this.

I am trying to test the Fragment in isolation.

public class RecipeListView
        extends MvpFragment<RecipeListViewContract, RecipeListPresenterImp>
        implements RecipeListViewContract {

    @VisibleForTesting
    private void setupDataBinding(List<Recipe> recipeList) {
        recipeAdapter = new RecipeAdapter(recipeList);
        RecyclerView.LayoutManager layoutManager
                = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        rvRecipeList.setLayoutManager(layoutManager);
        rvRecipeList.setAdapter(recipeAdapter);
    }

    @Override
    public void displayRecipeData(List<Recipe> recipeList) {
        /* Verify this get called only once */
        setupDataBinding(recipeList);

        recipeItemListener.onRecipeItem();
    }
}

This is how I am testing. I have added the VisibleForTesting thinking I could help. And I have tried using the spy.

public class RecipeListViewTest {
    private RecipeListView fragment;
    @Mock RecipeListPresenterContract presenter;
    @Mock RecipeItemListener recipeItemListener;
    @Mock List<Recipe> recipe;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(RecipeListViewTest.this);
        fragment = RecipeListView.newInstance();
    }

    @Test
    public void testShouldGetAllRecipes() {
        fragment.displayRecipeData(recipe);
        RecipeListView spy = Mockito.spy(fragment);

        verify(recipeItemListener, times(1)).onRecipeItem();
    }
}

What would be the best way to test the above in isolation?

Many thanks for any advice.

like image 380
ant2009 Avatar asked Jun 18 '17 08:06

ant2009


2 Answers

to prevent real method being called use: Mockito.doNothing().when(spy).onRecipeItem();

here you have minimum sample how to use it:

public class ExampleUnitTest {
    @Test
    public void testSpyObject() throws Exception {
        SpyTestObject spyTestObject = new SpyTestObject();
        SpyTestObject spy = Mockito.spy(spyTestObject);

        Mockito.doNothing().when(spy).methodB();

        spy.methodA();
        Mockito.verify(spy).methodB();
    }

    public class SpyTestObject {

        public void methodA() {
            methodB();
        }
        public void methodB() {
            throw new RuntimeException();
        }
    }

}

like image 135
RadekJ Avatar answered Oct 12 '22 23:10

RadekJ


I want to mock these calls as I don't want to call the real object on the RecyclerView. I just want to verify, that setupDataBinding() gets called.

You haven't created enough seams in order to perform that.

What if you declare a contract, which describes how the "setup data binding" will occur? In other words, what if you create an interface with method void setupDataBinding(...)? Then RecipeListView will hold an instance of that interface as a dependency. Thus, RecipeListView won't ever know how exactly this setup will take place: one thing it knows - the dependency he holds has "signed the contract" and took the responsibility to perform the job.

Normally, you would pass that dependency via constructor, but because Fragment is a specific case, you can acquire dependencies in onAttach():

interface Setupper {
    void setupDataBinding(List<Recipe> recipes, ...);
}

class RecipeListView extends ... {

    Setupper setupper;

    @Override public void onAttach(Context context) {
        super.onAttach(context);

        // Better let the Dependency Injection tool (e.g. Dagger) provide the `Setupper`
        // Or initialize it here (which is not recommended)
        Setupper temp = ...
        initSetupper(temp);
    }

    void initSetupper(Setupper setupper) {
        this.setupper = setupper;
    }

    @Override
    public void displayRecipeData(List<Recipe> recipes) {
        // `RecipeListView` doesn't know what exactly `Setupper` does
        // it just delegates the work
        setupper.setupDataBinding(recipes, ...);

        recipeItemListener.onRecipeItem();
    }
}

What does this give to you? Now you have a seam. Now you are not dependent on implementation, you are dependent on a contract.

public class RecipeListViewTest {

    @Mock Setupper setupper;
    List<Recipe> recipe = ...; // initialize, no need to mock it
    ...

    private RecipeListView fragment;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        fragment = new RecipeListView();
        fragment.initSetupper(setupper);
    }

    @Test
    public void testShouldGetAllRecipes() {
        fragment.displayRecipeData(recipes);

        // You do not care what happens behind this call
        // The only thing you care - is to test whether is has been executed
        verify(setupper).setupDataBinding(recipe, ...);
        // verify(..) is the same as verify(.., times(1))
    }
}

I would strongly advice Misko Hevery's "Writing Testable Code" book, which illustrates all the techniques with examples and in concise manner (38 pages).

like image 30
azizbekian Avatar answered Oct 13 '22 00:10

azizbekian