如何对实现接口的 FragmentDialog 和 Activity 之间的交互进行单元测试

How to unit test interaction between FragmentDialog and Activity which implements interface

我有 MainActivity 显示 FragmentDialog (EditIntervalFragment) 以捕获用户的输入。 Activity 实现了 EditIntervalListener 接口。在 onAtach 方法片段中,将 activity 转换为 EditIntervalListener。 我想测试我的 EditIntervalFragment 是否使用正确的参数正确调用了 EditIntervalListener 方法。

我最初的意图是使用 Roblectric 和 Mockito。以下代码几乎可以工作。

@Test
public void shouldCallInterfaceAfterModify() {
    MainActivity hostActivity = Robolectric.setupActivity(MainActivity.class);
    EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
    editIntervalFragment.show(hostActivity.getSupportFragmentManager(), "test");
    AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
    assertNotNull(dialog);

    EditIntervalFragment.EditIntervalListener activity = Mockito.spy(hostActivity);
    dialog.findViewById(android.R.id.button1).performClick();
    verify(activity).onIntervalChanged(0,TEST_NAME,TEST_DURATION);
}

此代码使用真实 MainActivity 的问题。这意味着所有MainActivity的逻辑都会被执行。我想避免这种情况。我该怎么做?

更新 我找到了一种不调用 real MainActivity 的方法。我创建了另一个 activity,只是为了测试。

public class ActivityTest extends FragmentActivity implements EditIntervalFragment.EditIntervalListener {

    //empty methods here
}

我的测试现在看起来像这样

@Test
public void shouldCallInterfaceAfterModify() {
    ActivityTest hostActivity = Robolectric.setupActivity(ActivityTest.class);
    ActivityTest spy = Mockito.spy(hostActivity);
    EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
    editIntervalFragment.show(spy.getSupportFragmentManager(), "test");
    AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
    assertNotNull(dialog);

    dialog.findViewById(android.R.id.button1).performClick();
    verify(spy).onIntervalChanged(0, TEST_NAME, TEST_DURATION);
}

但是在测试执行后我收到错误消息说只有 spy.getSupportFragmentManager() 被调用了。我 100% 确定应该调用 onIntervalChanged

寻求帮助。我怎样才能实施这种测试?

我最终得到了这个解决方案。创建一个 Activity 实现接口并跟踪所有交互。

public class ActivityTest extends FragmentActivity implements EditIntervalFragment.EditIntervalListener {

    public int mIntervalChangedCalls = 0;
    public int mPosition;
    public String mName;
    public long mDurationMillSec;

    @Override
    public void onIntervalChanged(int position, String name, long durationMillSec) {
        mIntervalChangedCalls++;
        mPosition = position;
        mName = name;
        mDurationMillSec = durationMillSec;
    }
}

我的测试是这样的

@Test
public void shouldCallOnIntervalChanged() {
    ActivityTest hostActivity = Robolectric.setupActivity(ActivityTest.class);
    EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
    editIntervalFragment.show(hostActivity.getSupportFragmentManager(), "test");
    AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
    assertNotNull(dialog);

    dialog.findViewById(android.R.id.button1).performClick();
    assertThat(hostActivity.mIntervalChangedCalls).isEqualTo(1);
    assertThat(hostActivity.mPosition).isEqualTo(0);
    assertThat(hostActivity.mName).isEqualTo(TEST_NAME);
    assertThat(hostActivity.mDurationMillSec).isEqualTo(TEST_DURATION);
}

我对创建一个单独的 class 仅用于测试目的并不完全满意。我想同样可以用 Mockito 或 Robolectric 实现,但我不知道如何实现。

所以我仍然愿意接受任何想法或建议。如果一周内没有人给出更好的解决方案,我会接受我自己的答案。

当你不控制生命周期时,让工作成为间谍总是很有挑战性的。

我们通常做的是将所有不相关的功能提取到实用程序 classes 并在测试中模拟它们。它还有助于应用程序的设计(单一 class 责任规则)。

当然,这取决于您是否对这些数据进行处理。如果它只是数据 class,那么我将 Factory 用于创建此数据 classes 并再次在测试中模拟它。所有这些都需要适当的 DI(看 Dagger)。

您的方法没有任何问题,但它不会强迫您将您的应用程序视为相互交互的小部件。但同时它带来了更多的复杂性,稍后会得到回报