使用Dagger与Robolectric测试MVP架构Fragment遇空指针问题求助
首先,你遇到的NullPointerException原因很明确:你的GalleryFragment里硬编码调用了MainApplication.getComponent(),但Robolectric测试中启动的是TestApplication,这意味着MainApplication的component字段从未被初始化(因为MainApplication的onCreate()根本没执行),所以调用inject(this)时自然会抛出空指针异常。
下面分两种方案帮你解决问题:
方案一:快速修复当前硬编码问题
我们可以通过抽象接口的方式,让MainApplication和TestApplication都实现同一个接口,这样Fragment就不需要依赖具体的Application类,而是通过接口获取Component。
- 定义Component获取接口
public interface HasAppComponent { AppComponent getAppComponent(); }
- 让两个Application类实现该接口
修改MainApplication:
public class MainApplication extends Application implements HasAppComponent { private AppComponent component; @Override public void onCreate() { super.onCreate(); Realm.init(this); component = buildComponent(); } @Override public AppComponent getAppComponent() { return component; } protected AppComponent buildComponent(){ return DaggerAppComponent .builder() .dIModules(new DIModules()) .build(); } }
修改TestApplication:
public class TestApplication extends Application implements HasAppComponent { private AppComponent component; @Override public void onCreate() { super.onCreate(); component = buildComponent(); } @Override public AppComponent getAppComponent() { return component; } protected AppComponent buildComponent(){ return DaggerAppComponent.builder() .dIModules(new mockDIModules()) .build(); } }
- 修改GalleryFragment的注入逻辑
把硬编码的MainApplication.getComponent()替换为通过接口获取:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); HasAppComponent app = (HasAppComponent) getActivity().getApplication(); app.getAppComponent().inject(this); mGalleryFragmentPresenter.initialize(mRepository, value); }
这样调整后,不管是正式环境的MainApplication还是测试环境的TestApplication,Fragment都能正确获取到Component并完成注入,NPE问题就能解决。
方案二:迁移到Dagger Android(推荐)
你提到的AndroidInjector方案完全可行,这是Dagger官方为Android平台设计的最佳实践,能彻底消除手动调用inject()的代码,让依赖注入更自动化、更易维护。
结合你的依赖版本(dagger-android-support:2.14.1),迁移步骤如下:
1. 重构AppComponent
让AppComponent继承AndroidInjector<MainApplication>,并引入AndroidSupportInjectionModule(用于支持Fragment等Android组件的注入):
@Singleton @Component(modules = { AndroidSupportInjectionModule.class, DIModules.class, ActivityBindingModule.class }) public interface AppComponent extends AndroidInjector<MainApplication> { @Component.Builder abstract class Builder extends AndroidInjector.Builder<MainApplication> {} }
2. 创建Activity绑定Module
这个Module用于声明Activity对应的注入器,Fragment的注入会依赖于所属Activity的注入器:
@Module public abstract class ActivityBindingModule { // 绑定MainActivity的注入器,同时关联Fragment的Module @ContributesAndroidInjector(modules = FragmentBindingModule.class) abstract MainActivity contributeMainActivityInjector(); }
3. 创建Fragment绑定Module
声明GalleryFragment的注入器:
@Module public abstract class FragmentBindingModule { @ContributesAndroidInjector abstract GalleryFragment contributeGalleryFragmentInjector(); }
4. 修改MainApplication
让它实现HasAndroidInjector,并注入DispatchingAndroidInjector来处理组件注入:
public class MainApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate() { super.onCreate(); Realm.init(this); // 初始化Dagger并注入Application自身 DaggerAppComponent.create().inject(this); } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; } }
5. 修改MainActivity
让它继承DaggerAppCompatActivity(如果你的Activity是继承AppCompatActivity的话),这样Activity会自动完成注入,同时会自动处理其内部Fragment的注入:
public class MainActivity extends DaggerAppCompatActivity { // 不需要手动调用inject,Dagger会自动处理 }
6. 修改GalleryFragment
让它继承DaggerFragment,这样Fragment会自动被注入,完全不需要手动调用inject():
public class GalleryFragment extends DaggerFragment { @Inject public MainActivityRepository mRepository; @Inject public GalleryFragmentPresenter mGalleryFragmentPresenter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 这里不需要任何手动注入代码,Dagger已经完成了依赖注入 mGalleryFragmentPresenter.initialize(mRepository, value); } }
测试环境适配
测试时的TestApplication只需要同样实现HasAndroidInjector,并使用测试用的Component即可:
public class TestApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate() { super.onCreate(); DaggerTestAppComponent.create().inject(this); } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; } }
对应的TestAppComponent使用mock的Module:
@Singleton @Component(modules = { AndroidSupportInjectionModule.class, MockDIModules.class, TestActivityBindingModule.class }) public interface TestAppComponent extends AndroidInjector<TestApplication> { @Component.Builder abstract class Builder extends AndroidInjector.Builder<TestApplication> {} }
总结
- 方案一可以快速修复当前的NPE问题,改动较小;
- 方案二是长期的最优解,遵循Dagger官方推荐的Android开发模式,彻底消除手动注入的冗余代码,让代码结构更清晰,测试也更方便。
内容的提问来源于stack exchange,提问作者Kei

