如何使用Dagger2将活动范围的依赖项替换为模拟

时间:2015-08-29 00:33:10

标签: android unit-testing dependency-injection dagger dagger-2

我的Activity中有一个作用域依赖项,我想用一些模拟测试该活动。我已经阅读了有关建议在测试期间用测试组件替换Application组件的不同方法,但我想要的是替换Activity组件。

例如,我想在我的MVP设置中测试针对模拟演示者的Activity。

我相信通过调用Activity上的setComponent()替换组件将无法工作,因为Activity依赖已经通过字段注入注入,因此在测试期间,将使用真实对象。

如何解决此问题? Dagger1怎么样?它有同样的问题吗?

3 个答案:

答案 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();

或者您可以将提供的类和注入目标放到他们自己的“元组件”界面中,ApplicationComponentTestApplicationComponent延伸到该界面。

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