浓缩咖啡、Dagger2在BaseActivity上设置ViemodelProvider.Factory

14

我有一个抽象的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

当一个活动有一个fragment时,我可以做类似以下的事情:

@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);

因为onCreate方法会立即被调用,所以没有办法动态地设置viewModelFactory。 在onCreate方法被调用之前获取Activity对象的方法似乎也不存在。

如何解决这个问题?

注意:我使用Dagger 2.11 with AndroidInjector。
另请参阅我昨天发布的这个问题的后续信息:Inject ViewModelFactory.Provider in activity for espresso testing

4个回答

0

我通过重写 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 );
            }
        }
    };
} 

这似乎是不可能找到另一种解决方案,我不明白为什么这么困难!你能分享更多吗?它放在一个AppTest类中对吧?我只能重写DispatchingAndroidInjector<Activity> activityInjector()... - Daniel Wilson

0

可以通过在@Before方法中注册自定义的ActivityLifecycleCallbacks来设置注入的活动属性。

示例:

 @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);


}

0

你可以创建自己的测试规则

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

更多信息请点击这里


1
我不相信在beforeActivityLaunched中我将能够访问activity对象。那么我该如何设置viewModelFactory呢?你能给一个具体的例子吗? - html_programmer
请阅读以下内容,介绍如何使用Dagger 2、Mockito和自定义JUnit规则进行Android测试。 - anatoli

0

尝试覆盖dagger模块以提供ViewModelProvider.Factory。

  • 在app/build.gradle中更改testInstrumentationRunner为'com.eusecom.attendance.MockTestRunner'

  • 在MockTestRunner中调用MockYourApplication.class

  • 在MockYourApplication.class中创建新的Mock dagger组件

  • 在运行testActivity之前,覆盖dagger组件和模块

查看示例https://github.com/eurosecom/Attendance/blob/master/app/src/androidTest/java/com/eusecom/attendance/DgAeaActivityTest.java

我没有使用新的dagger 2.11与AndroidInjector(我使用了旧的dagger2模式),但可能会有所帮助。


如果我找不到一种简单的方法,我将放弃使用新的API以便能够进行适当的测试。谢谢。 - html_programmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接