我的Activity中有一个作用域依赖项,我想用一些模拟测试该活动。我已经阅读了有关建议在测试期间用测试组件替换Application组件的不同方法,但我想要的是替换Activity组件。
例如,我想在我的MVP设置中测试针对模拟演示者的Activity。
我相信通过调用Activity上的setComponent()替换组件将无法工作,因为Activity依赖已经通过字段注入注入,因此在测试期间,将使用真实对象。
如何解决此问题? Dagger1怎么样?它有同样的问题吗?
答案 0 :(得分:2)
注入组件
首先,创建一个静态类作为Activity的工厂。我看起来有点像这样:
public class ActivityComponentFactory {
private static ActivityComponentFactory sInstance;
public static ActivityComponentFactory getInstance() {
if(sInstance == null) sInstance = new ActivityComponentFactory();
return sInstance;
}
@VisibleForTesting
public static void setInstance(ActivityComponentFactory instance) {
sInstance = instance;
}
private ActivityComponentFactory() {
// Singleton
}
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.create();
}
}
然后在你的活动中做ActivityComponentFactory.getInstance().createActivityComponent().inject(this);
。
为了进行测试,您可以在创建活动之前替换方法中的工厂。
提供模拟
正如@ EpicPandaForce的回答所表明的那样,官方支持的方式目前涉及大量的样板和复制/粘贴代码。 Dagger 2团队需要提供一种更简单的方法来部分覆盖模块。
直到他们这样做,这是我的非正式方式:只需扩展模块。
我们假设你想用模拟替换你的ListViewPresenter。假设你有一个如下所示的PresenterModule:
@Module @ActivityScope
public class PresenterModule {
@ActivityScope
public ListViewPresenter provideListViewPresenter() {
return new ListViewPresenter();
}
@ActivityScope
public SomeOtherPresenter provideSomeOtherPresenter() {
return new SomeOtherPresenter();
}
}
您可以在测试设置中执行此操作:
ActivityComponentFactory.setInstance(new ActivityComponentFactory() {
@Override
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.builder()
.presenterModule(new PresenterModule() {
@Override
public ListViewPresenter provideListViewPresenter() {
// Note you don't have to use Mockito, it's just what I use
return Mockito.mock(ListViewPresenter.class);
}
})
.build();
}
});
... 正常工作!
请注意,您不必在@Provides
方法中加入@Override
注释。事实上,如果你这样做,那么Dagger 2代码生成就会失败。
这是有效的,因为模块只是简单的工厂 - 生成的Component类负责缓存作用域实例的实例。 @Scope
注释由代码生成器使用,但在运行时无关紧要。
答案 1 :(得分:0)
您无法覆盖Dagger2中的模块[编辑:您可以,只是不要在模拟上指定@Provides
注释),这显然是正确的解决方案:只需使用builder().somethingModule(new MockSomethingModule()).build()
并完成它!
如果您认为无法进行模拟,那么我会看到两个可能解决此问题的方法。你可以使用模块来包含一个可插拔的“提供者”,它可以改变它的实现(我不赞成这个,因为它太冗长了!)
public interface SomethingProvider {
Something something(Context context);
}
@Module
public class SomethingModule {
private SomethingProvider somethingProvider;
public SomethingModule(SomethingProvider somethingProvider) {
this.somethingProvider = somethingProvider;
}
@Provides
@Singleton
public Something something(Context context) {
return somethingProvider.something(context);
}
}
public class ProdSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new SomethingImpl(context);
}
}
public class TestSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new MockSomethingImpl(context);
}
}
SomethingComponent somethingComponent = DaggerSomethingComponent.builder()
.somethingModule(new SomethingModule(new ProdSomethingProvider()))
.build();
或者您可以将提供的类和注入目标放到他们自己的“元组件”界面中,ApplicationComponent
和TestApplicationComponent
延伸到该界面。
public interface MetaApplicationComponent {
Something something();
void inject(MainActivity mainActivity);
}
@Component(modules={SomethingModule.class})
@Singleton
public interface ApplicationComponent extends MetaApplicationComponent {
}
@Component(modules={MockSomethingModule.class})
@Singleton
public interface MockApplicationComponent extends MetaApplicationComponent {
}
第三个解决方案就是像@vaughandroid的回答一样扩展模块。请参考,这是正确的做法。
至于活动范围的组件......与我在这里提到的相同,它只是一个不同的范围,真的。
答案 2 :(得分:0)
我发现以下帖子解决了这个问题: http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
首先需要允许修改活动的组件:
@Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
并在测试用例中修改它
@Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
DemoApplication app
= (DemoApplication) instrumentation.getTargetContext().getApplicationContext();
TestComponent component = DaggerMainActivityTest_TestComponent.builder()
.mockClockModule(new MockClockModule())
.build();
app.setComponent(component);
component.inject(this);
}