Espresso, Dagger2 установить ViemodelProvider.Factory на BaseActivity
У меня есть абстрактная AccountRequiredActivity, которая выглядит так (и отлично работает):
public abstract class AccountRequiredActivity extends LifecycleActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private AccountViewModel accountViewModel;
public abstract void doOnCreate(Bundle savedInstanceState);
public abstract void doOnResume();
@Override
protected final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading_app);
AndroidInjection.inject(this);
accountViewModel = ViewModelProviders.of(this, viewModelFactory).get(AccountViewModel.class);
if(!accountViewModel.isAuthenticated()) {
redirectToLogin();
} else {
doOnCreate(savedInstanceState);
};
}
@Override
protected void onResume() {
super.onResume();
if(!accountViewModel.isAuthenticated()) {
redirectToLogin();
} else {
doOnResume();
};
}
private void redirectToLogin() {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
}
Проблема во время тестов заключается в том, что у меня нет способа установить viewModelFactory
в действие.
Когда у объекта есть фрагмент, я могу просто сделать что-то вроде:
@Before
public void init() {
LoginFragment fragment = LoginFragment.newInstance();
viewModel = mock(AccountViewModel.class);
when(viewModel.getAuthenticatedUserResource()).thenReturn(authenticatedUser);
fragment.viewModelFactory = ViewModelUtil.createFor(viewModel);
activityRule.getActivity().setFragment(fragment);
}
Однако в этом случае проблема заключается в том, что я использую это в своих тестах (HomeActivity расширяет AccountRequiredActivity):
@Rule
public ActivityTestRule<HomeActivity> activityTestRule = new ActivityTestRule<>(HomeActivity.class, true, false);
Таким образом, нет возможности динамически устанавливать viewModelFactory
, так как onCreate
немедленно вызывается. Кажется, что нет способа получить доступ к объекту Activity до вызова onCreate
.
Как решить эту проблему?
Примечание. Я использую Dagger 2.11 с AndroidInjector.
Также см. Этот вопрос, который я опубликовал вчера для последующей информации:
Inject ViewModelFactory.Provider в действии для тестирования эспрессо
Ответы
Ответ 1
Я решил проблему, переопределив метод AndroidInjector inject()
:
@Override
public AndroidInjector<Activity> activityInjector() {
return new AndroidInjector<Activity>() {
@Override
public void inject(Activity instance) {
AccountViewModel viewModel = mock( AccountViewModel.class );
if(instance instanceof TestHomeActivity) {
((TestHomeActivity) instance).viewModelFactory = ViewModelUtil.createFor( viewModel );
}
}
};
}
Ответ 2
вы можете создать собственный testrule
public class MyCustomRule<A extends MainActivity> extends ActivityTestRule<A> {
public MyCustomRule(Class<A> activityClass) {
super(activityClass);
}
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
// Maybe prepare some mock service calls
// Maybe override some depency injection modules with mocks
}
@Override
protected Intent getActivityIntent() {
Intent customIntent = new Intent();
// add some custom extras and stuff
return customIntent;
}
@Override
protected void afterActivityLaunched() {
super.afterActivityLaunched();
// maybe you want to do something here
}
@Override
protected void afterActivityFinished() {
super.afterActivityFinished();
// Clean up mocks
}
}
и установить как ActivityTestRule
@Rule
public ActivityTestRule<MainActivity> testRule = new MyCustomRule<>(MainActivity.class);
в beforeActivityLaunched()
, вы можете ввести viewModelFactory
подробнее здесь
Ответ 3
Попробуйте переопределить кинжальный модуль для обеспечения ViewModelProvider.Factory.
-
изменить testInstrumentationRunner 'com.eusecom.attendance.MockTestRunner' в app/build.gradle
-
вызов MockYourApplication.class в MockTestRunner
-
создать новый компонент Mock dagger в вашем MockYourApplication.class
-
перед запуском testActivity переопределить компонент и модуль кинжала
Смотрите пример https://github.com/eurosecom/Attendance/blob/master/app/src/androidTest/java/com/eusecom/attendance/DgAeaActivityTest.java
Я не использовал новый кинжал 2.11 с AndroidInjector (я использовал старый шаблон dagger2), но может быть, это помогает.
Ответ 4
Возможно установить атрибут введенной активности, зарегистрировав пользовательский ActivityLifecycleCallbacks в вашем TestApp в методе @Before.
Пример:
@Before
public void init(){
UserFragment fragment = UserFragment.create("foo");
viewModel = mock(UserViewModel.class);
when(viewModel.getUser()).thenReturn(userData);
when(viewModel.getRepositories()).thenReturn(repoListData);
navigationController = mock(NavigationController.class);
TestApp testApp = ((TestApp) InstrumentationRegistry.getContext().getApplicationContext());
testApp.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//will be called before the onCreate method of your activity
activity.setViewModelFactory(ViewModelUtil.createFor(viewModel));
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
fragment.viewModelFactory = ViewModelUtil.createFor(viewModel);
fragment.navigationController = navigationController;
fragment.dataBindingComponent = () -> fragmentBindingAdapters;
activityRule.getActivity().setFragment(fragment);
}