fabioCollini / DaggerMock

A JUnit rule to easily override Dagger 2 objects
Apache License 2.0
1.16k stars 91 forks source link

Support for @Component.Builder and @BindsInstance #52

Closed RoRoche closed 7 years ago

RoRoche commented 7 years ago

Here is my main component:

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ModuleApp.class,
        ActivityBuilder.class,
        ModuleMainActivity.class,
        ModuleFragmentSecond.class
})
public interface ComponentApp {
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(final App pApp);

        Builder moduleApp(final ModuleApp pModuleApp);

        ComponentApp build();
    }

    void inject(final App pApp);
}

Here is my Application subclass:

public class App extends Application implements HasActivityInjector {
    @Inject
    public DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    private ComponentApp mComponentApp;

    @Override
    public void onCreate() {
        super.onCreate();
        buildComponent();
        mComponentApp.inject(this);
    }

    private void buildComponent() {
        mComponentApp = DaggerComponentApp.builder()
                .application(this)
                .moduleApp(new ModuleApp(this))
                .build();
    }

    @VisibleForTesting
    public void setComponentApp(@NonNull final ComponentApp pComponentApp) {
        mComponentApp = pComponentApp;
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }
}

And here is my DaggerMockRule subclass:

public class AppTestRule extends DaggerMockRule<ComponentApp> {
    public AppTestRule() {
        super(ComponentApp.class, new ModuleApp(getApp()));
        set((final ComponentApp pComponent) -> getApp().setComponentApp(pComponent));
    }

    private static App getApp() {
        return (App) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
    }
}

When launching my test, I reach the following exception:

java.lang.RuntimeException: Error invoking method public fr.guddy.android_modular_reloaded.di.ComponentApp fr.guddy.android_modular_reloaded.di.DaggerComponentApp$Builder.build()
at it.cosenonjaviste.daggermock.ReflectUtils.invokeMethod(ReflectUtils.java:88)
at it.cosenonjaviste.daggermock.ObjectWrapper.invokeMethod(ObjectWrapper.java:63)
at it.cosenonjaviste.daggermock.ObjectWrapper.invokeMethod(ObjectWrapper.java:59)
at it.cosenonjaviste.daggermock.DaggerMockRule.setupComponent(DaggerMockRule.java:128)
at it.cosenonjaviste.daggermock.DaggerMockRule.access$000(DaggerMockRule.java:36)
at it.cosenonjaviste.daggermock.DaggerMockRule$1.evaluate(DaggerMockRule.java:104)
at android.support.test.internal.statement.UiThreadStatement.evaluate(UiThreadStatement.java:55)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:270)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1879)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at it.cosenonjaviste.daggermock.ReflectUtils.invokeMethod(ReflectUtils.java:86)
... 30 more
Caused by: java.lang.IllegalStateException: fr.guddy.android_modular_reloaded.App must be set
at fr.guddy.android_modular_reloaded.di.DaggerComponentApp$Builder.build(DaggerComponentApp.java:111)
... 32 more

because of the IllegalStateException thrown by the Dagger2's generated code as follows:

  private static final class Builder implements ComponentApp.Builder {
    private ModuleMainActivity moduleMainActivity;

    private App application;

    @Override
    public ComponentApp build() {
      if (moduleMainActivity == null) {
        this.moduleMainActivity = new ModuleMainActivity();
      }
      if (application == null) {
        throw new IllegalStateException(App.class.getCanonicalName() + " must be set");
      }
      return new DaggerComponentApp(this);
    }
fabioCollini commented 7 years ago

I have pushed a new version to support Dagger Android (you can try it using version 0.7.1-beta1). You must add an invocation to method customizeBuilder to set the application, an example is available here. In this version Dagger Android is supported in Espresso test to replace objects defined in Application component, I'll continue to investigate to find a way to support it in JUnit tests and for subcomponents and dependent components.

RoRoche commented 7 years ago

Awesome! It works like a charm. Thanks a lot.