How can I replace Activity scoped dependencies with mocks using Dagger2

↘锁芯ラ 提交于 2019-12-04 12:40:51

Injecting the Component

First, you create a static class to act as a factory for your Activity. Mine looks a little like this:

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

Then just do ActivityComponentFactory.getInstance().createActivityComponent().inject(this); inside your Activities.

For testing, you can replace the factory in your method, before the Activity is created.

Providing mocks

As @EpicPandaForce's answer makes clear, doing this the officially-supported way currently involves a lot of boilerplate and copy/pasted code. The Dagger 2 team need to provide a simpler way of partially overriding Modules.

Until they do though, here's my unnoficial way: Just extend the module.

Let's say you want to replace your ListViewPresenter with a mock. Say you have a PresenterModule which looks like this:

@Module @ActivityScope
public class PresenterModule {

    @ActivityScope
    public ListViewPresenter provideListViewPresenter() {
        return new ListViewPresenter();
    }

    @ActivityScope
    public SomeOtherPresenter provideSomeOtherPresenter() {
        return new SomeOtherPresenter();
    }
}

You can just do this in your test setup:

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

...and it just works!

Note that you don't have to include the @Provides annotation on the @Override method. In fact, if you do then the Dagger 2 code generation will fail.

This works because the Modules are just simple factories - the generated Component classes take care of caching instances of scoped instances. The @Scope annotations are used by the code generator, but are irrelevant at runtime.

You cannot override modules in Dagger2 [EDIT: you can, just don't specify the @Provides annotation on the mock), which would obviously be the proper solution: just use the builder().somethingModule(new MockSomethingModule()).build() and be done with it!

If you thought mocking is not possible, then I would have seen two possible solutions to this problem. You can either use the modules to contain a pluggable "provider" that can have its implementation changed (I don't favor this because it's just too verbose!)

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

Or you can bring the provided classes and injection targets out into their own "metacomponent" interface, which your ApplicationComponent and your TestApplicationComponent extend from.

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 {
}

The third solution is to just extend the modules like in @vaughandroid 's answer. Refer to that, that is the proper way of doing it.

As for activity scoped components... same thing as I mentioned here, it's just a different scope, really.

I've found the following post that solves the problem: http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html

You need first to allow to modify the component of the activity:

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

And modify it in the test case

@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);
  }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!