【发布时间】:2017-03-22 00:29:14
【问题描述】:
编辑:当心!我已经删除了这个问题中提到的旧存储库。请参阅我自己对该问题的回答以获取可能的解决方案,并随时对其进行改进!
我指的是我的帖子here。现在我走得更远了。我还指的是我的 github 项目中的两个分支:
- 实验[分支号。 1](存储库已删除)
- 实验[分支号。 2](存储库已删除)
在旧帖子中,我尝试在仪器测试中将组件交换为测试组件。如果我有一个ApplicationComponent,这现在可以工作了,在单例范围内。但是,如果我有一个具有自定义 @PerActivity 范围的 ActivityComponent,它确实不起作用。问题不是范围,而是组件到 TestComponent 的交换。
我的ActivityComponent 有一个ActivityModule:
@PerActivity
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
// TODO: Comment this out for switching back to the old approach
void inject(MainFragment mainFragment);
// TODO: Leave that for witching to the new approach
void inject(MainActivity mainActivity);
}
ActivityModule 提供MainInteractor
@Module
public class ActivityModule {
@Provides
@PerActivity
MainInteractor provideMainInteractor () {
return new MainInteractor();
}
}
我的TestActivityComponent 使用TestActivityModule:
@PerActivity
@Component(modules = TestActivityModule.class)
public interface TestActivityComponent extends ActivityComponent {
void inject(MainActivityTest mainActivityTest);
}
TestActvityModule 提供了一个FakeInteractor:
@Module
public class TestActivityModule {
@Provides
@PerActivity
MainInteractor provideMainInteractor () {
return new FakeMainInteractor();
}
}
我的MainActivity 有一个getComponent() 方法和一个setComponent() 方法。使用后者,您可以将组件交换为 Instrumentation Test 中的测试组件。这是活动:
public class MainActivity extends BaseActivity implements MainFragment.OnFragmentInteractionListener {
private static final String TAG = "MainActivity";
private Fragment currentFragment;
private ActivityComponent activityComponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeInjector();
if (savedInstanceState == null) {
currentFragment = new MainFragment();
addFragment(R.id.fragmentContainer, currentFragment);
}
}
private void initializeInjector() {
Log.i(TAG, "injectDagger initializeInjector()");
activityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule())
.build();
activityComponent.inject(this);
}
@Override
public void onFragmentInteraction(final Uri uri) {
}
ActivityComponent getActivityComponent() {
return activityComponent;
}
@VisibleForTesting
public void setActivityComponent(ActivityComponent activityComponent) {
Log.w(TAG, "injectDagger Only call this method to swap test doubles");
this.activityComponent = activityComponent;
}
}
如您所见,此活动使用MainFragment。在片段的onCreate() 中注入了组件:
public class MainFragment extends BaseFragment implements MainView {
private static final String TAG = "MainFragment";
@Inject
MainPresenter mainPresenter;
private View view;
public MainFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "injectDagger onCreate()");
super.onCreate(savedInstanceState);
// TODO: That approach works
// ((AndroidApplication)((MainActivity) getActivity()).getApplication()).getApplicationComponent().inject(this);
// TODO: This approach is NOT working, see MainActvityTest
((MainActivity) getActivity()).getActivityComponent().inject(this);
}
}
然后在测试中我将ActivityComponent 与TestApplicationComponent 交换:
public class MainActivityTest{
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class, true, false);
private MainActivity mActivity;
private TestActivityComponent mTestActivityComponent;
// TODO: That approach works
// private TestApplicationComponent mTestApplicationComponent;
//
// private void initializeInjector() {
// mTestApplicationComponent = DaggerTestApplicationComponent.builder()
// .testApplicationModule(new TestApplicationModule(getApp()))
// .build();
//
// getApp().setApplicationComponent(mTestApplicationComponent);
// mTestApplicationComponent.inject(this);
// }
// TODO: This approach does NOT work because mActivity.setActivityComponent() is called after MainInteractor has already been injected!
private void initializeInjector() {
mTestActivityComponent = DaggerTestActivityComponent.builder()
.testActivityModule(new TestActivityModule())
.build();
mActivity.setActivityComponent(mTestActivityComponent);
mTestActivityComponent.inject(this);
}
public AndroidApplication getApp() {
return (AndroidApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
}
// TODO: That approach works
// @Before
// public void setUp() throws Exception {
//
// initializeInjector();
// mActivityRule.launchActivity(null);
// mActivity = mActivityRule.getActivity();
// }
// TODO: That approach does not works because mActivity.setActivityComponent() is called after MainInteractor has already been injected!
@Before
public void setUp() throws Exception {
mActivityRule.launchActivity(null);
mActivity = mActivityRule.getActivity();
initializeInjector();
}
@Test
public void testOnClick_Fake() throws Exception {
onView(withId(R.id.edittext)).perform(typeText("John"));
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello Fake"))));
}
@Test
public void testOnClick_Real() throws Exception {
onView(withId(R.id.edittext)).perform(typeText("John"));
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello John"))));
}
}
活动测试运行但使用了错误的Component。这是因为活动和片段onCreate() 在组件交换之前运行。
如您所见,我有一个已注释的旧方法,即我将ApplicationComponent 绑定到应用程序类。这是有效的,因为我可以在开始活动之前构建依赖项。但是现在有了ActivityComponent,我必须在初始化注入器之前启动活动。因为否则我无法设置
mActivity.setActivityComponent(mTestActivityComponent);
因为mActivity 在注入器初始化后启动活动时将为空。 (见MainActivityTest)
那么我如何拦截MainActivity 和MainFragment 以使用TestActivityComponent?
【问题讨论】:
-
您可以通过询问“如何在 Activity 或 Fragment 范围内交换测试替身”来将其变成一个非常好的问题。到目前为止,我看到的大多数示例都有更换应用程序范围组件的说明,但没有用于更下方的注入站点。
-
是的,你是对的。谢谢。
标签: android unit-testing android-testing dagger-2 android-instrumentation