使用 Mockito 单独测试片段 class
Testing a Fragment class in isolation using Mockito
已添加 @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(recipe)。也许我做错了。
@Test
public void testShouldGetAllRecipes() {
RecipeListView spy = Mockito.spy(fragment);
doNothing().when(spy).setupDataBinding(recipe);
fragment.displayRecipeData(recipe);
verify(recipeItemClickListener, times(1)).onRecipeItemClick();
}
我正在尝试测试 Fragment
class 中的方法,如下所示。但是,我正在尝试模拟这些方法以验证这些方法被调用的次数是否正确。但是,问题是我有一个 private
方法 setupDataBinding(...)
设置在从 displayRecipeData(...)
调用的 RecyclerView
上。我想模拟这些调用,因为我不想调用 RecyclerView
上的真实对象。我只想验证 setupDataBinding(...)
是否被调用。
我尝试过使用间谍和 VisibleForTesting
,但仍然不确定如何使用。
我正在尝试单独测试 Fragment。
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
认为我可以提供帮助。我试过使用间谍。
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();
}
}
单独测试上述内容的最佳方法是什么?
非常感谢您的任何建议。
为了防止真正的方法被调用使用: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();
}
}
}
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.
您还没有创建足够的接缝来执行此操作。
如果您声明一个描述 "setup data binding" 将如何发生的合同怎么办?换句话说,如果您使用方法 void setupDataBinding(...)
创建一个接口会怎样?然后 RecipeListView
将持有该接口的一个实例作为依赖项。因此,RecipeListView
永远不会知道这个设置将如何进行:它知道一件事 - 他拥有的依赖关系 "signed the contract" 并负责执行这项工作。
通常,您会通过构造函数传递该依赖项,但是因为 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();
}
}
这给了你什么?现在你有一个接缝。现在您不依赖实施,您依赖合同。
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's "Writing Testable Code" 这本书,它通过示例以简明的方式说明了所有技术(38 页)。
有一个普遍的经验法则:测试单位的功能比测试它的工作方式要好得多。
考虑到这一点问自己一个问题 - 为什么我首先要模拟 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());
}
这可能需要添加几个 getters/setters 以使整个事情更易于测试。
已添加 @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(recipe)。也许我做错了。
@Test
public void testShouldGetAllRecipes() {
RecipeListView spy = Mockito.spy(fragment);
doNothing().when(spy).setupDataBinding(recipe);
fragment.displayRecipeData(recipe);
verify(recipeItemClickListener, times(1)).onRecipeItemClick();
}
我正在尝试测试 Fragment
class 中的方法,如下所示。但是,我正在尝试模拟这些方法以验证这些方法被调用的次数是否正确。但是,问题是我有一个 private
方法 setupDataBinding(...)
设置在从 displayRecipeData(...)
调用的 RecyclerView
上。我想模拟这些调用,因为我不想调用 RecyclerView
上的真实对象。我只想验证 setupDataBinding(...)
是否被调用。
我尝试过使用间谍和 VisibleForTesting
,但仍然不确定如何使用。
我正在尝试单独测试 Fragment。
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
认为我可以提供帮助。我试过使用间谍。
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();
}
}
单独测试上述内容的最佳方法是什么?
非常感谢您的任何建议。
为了防止真正的方法被调用使用: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();
}
}
}
I want to mock these calls as I don't want to call the real object on the
RecyclerView
. I just want to verify, thatsetupDataBinding()
gets called.
您还没有创建足够的接缝来执行此操作。
如果您声明一个描述 "setup data binding" 将如何发生的合同怎么办?换句话说,如果您使用方法 void setupDataBinding(...)
创建一个接口会怎样?然后 RecipeListView
将持有该接口的一个实例作为依赖项。因此,RecipeListView
永远不会知道这个设置将如何进行:它知道一件事 - 他拥有的依赖关系 "signed the contract" 并负责执行这项工作。
通常,您会通过构造函数传递该依赖项,但是因为 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();
}
}
这给了你什么?现在你有一个接缝。现在您不依赖实施,您依赖合同。
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's "Writing Testable Code" 这本书,它通过示例以简明的方式说明了所有技术(38 页)。
有一个普遍的经验法则:测试单位的功能比测试它的工作方式要好得多。
考虑到这一点问自己一个问题 - 为什么我首先要模拟 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());
}
这可能需要添加几个 getters/setters 以使整个事情更易于测试。