使用Mockito单独测试Fragment类

时间:2017-06-18 08:32:23

标签: java android unit-testing mockito android-testing

添加了@VisibleForTesting并受到保护。我的测试现在可以用这个方法:

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

使用间谍对象更新了测试用例:但是,即使我创建了一个将被调用的间谍模拟,真正的setupDataBinding(配方)也会被调用。也许我做错了。

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

    fragment.displayRecipeData(recipe);

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

我正在尝试测试Fragment课程中的方法,如下所示。但是,我试图模拟方法来验证方法被调用正确的次数。但是,问题是我在private上设置了setupDataBinding(...)方法RecyclerView。我想模仿这些调用,因为我不想在displayRecipeData(...)上调用真实对象。我只是想验证RecyclerView是否被调用。

我曾尝试使用间谍和setupDataBinding(...),但仍不确定如何执行此操作。

我试图孤立地测试片段。

VisibleForTesting

这就是我的测试方式。我添加了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(); } } 我认为可以提供的帮助。我尝试过使用间谍。

VisibleForTesting

单独测试上述内容的最佳方法是什么?

非常感谢您的任何建议。

3 个答案:

答案 0 :(得分:4)

以防止使用实际方法:Mockito.doNothing().when(spy).onRecipeItem();

这里有最低限度的样本使用方法:

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

}

答案 1 :(得分:3)

  

我想模仿这些来电,因为我不想在RecyclerView上调用真实对象。我只想验证,setupDataBinding()被调用。

您还没有创建足够的接缝来执行此操作。

如果您声明合同,该合同将描述&#34;设置数据绑定&#34;会发生?换句话说,如果使用方法void setupDataBinding(...)创建接口,该怎么办?然后RecipeListView将该接口的实例作为依赖项。因此,RecipeListView不知道这个设置究竟会如何发生:它知道的一件事 - 他所持有的依赖关系已签署合同&#34;并承担起履行职责的责任。

通常,您会通过构造函数传递该依赖项,但因为Fragment  is a specific case,您可以在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();
    }
}

这给你带来了什么?现在你有一个接缝。现在你依赖于实施,你依赖合同。

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

我强烈建议Misko Hevery "Writing Testable Code"本书,其中以示例和简洁的方式说明了所有技巧(38页)。

答案 2 :(得分:1)

有一个共同的经验法则是:测试单位的作用要好得多,而不是如何进行测试。

考虑到这一点,问问自己一个问题 - 为什么我会首先嘲笑setupDataBinding方法?它不进行任何外部调用,它只更改对象的状态。因此,测试此代码的更好方法是检查它是否以正确的方式更改状态:

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

     // Verifies whether RecipeAdapter has been initialized correctly
     RecipeAdapter recipeAdapter = fragment.getRecipeAdapter();
     assertNotNull(recipeAdapter);
     assertSame(recipeList, recipeAdapter.getRecipeList());

     // Verifies whethr RvRecipeList has been initialized correctly 
     RvRecipeList rvRecipeList = fragment.getRvRecipeList();
     assertNotNull(rvRecipeList);
     assertNotNull(rvRecipeList.getLayoutManager());
     assertSame(fragment.getRecipeAdapter(), rvRecipeList.getAdapter());
}

这可能需要添加几个getter / setter来使整个事情更容易测试。