РЕДАКТИРОВАТЬ: Будьте осторожны! Я удалил старый репозиторий, на который ссылается этот вопрос. Посмотрите мой собственный ответ на вопрос о возможном решении и не стесняйтесь улучшать его!
Я имею в виду свой пост здесь. Теперь я пришел немного дальше. Я также имею в виду две мои ветки в моем проекте github:
- Экспериментальный [отрасль №. 1] (репозиторий удален)
- Экспериментальный [отрасль №. 2] (репозиторий удален)
В старом посте я пытался поменять компоненты на тестовые компоненты в инструментальном тесте. Теперь это работает, если у меня есть ApplicationComponent
, находящийся в одноэлементной области. Но это не работает, если у меня есть ActivityComponent
с самоопределяемой областью действия @PerActivity
. Проблема заключается не в области действия, а в замене компонента на 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()
. С последним вы можете поменять компонент на тестовый компонент в инструментальном тесте. Вот активность:
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
?