2
votes

As per the Dagger 2 documentation

I am trying to set up a test environment according to Dagger2's documentation

(Just for context, I have successfully done this in Dagger 1.)

The problem, specifically, is that while Dagger2 correctly generates DaggerRoboDaggerComponent (as used in App.java), it does not generate DaggerTestRoboDaggerComponent (as used in MainActivityTest.java). I have checked the directory structure to make sure it's not hiding in some obscure place, and done the requisite clean, rebuild, Invalidate Caches / Restart, etc.

If someone could please let me know the error of my ways, I'd appreciate it. Thanks in advance.

You can clone the project here

Or, browse thru the project files below:

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "xx.robodagger"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.google.dagger:dagger:2.10'
    annotationProcessor "com.google.dagger:dagger-compiler:2.10"
    testCompile 'junit:junit:4.12'
    testCompile "org.robolectric:robolectric:3.3.1"
}

App.java

package xx.robodagger;

import android.app.Application;

public class App extends Application {

    static RoboDaggerComponent roboDaggerComponent;
    static RoboDaggerComponent getComponent() {
        return roboDaggerComponent;
    }

    @Override public void onCreate() {
        super.onCreate();
        roboDaggerComponent = DaggerRoboDaggerComponent.builder()
            .roboDaggerModule(new RoboDaggerModule())
            .build();
    }

}

Foo.java

package xx.robodagger;

class Foo {
    @Override public String toString() {
        return "foo";
   }
}

MainActivity.java

package xx.robodagger;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {

    @Inject Foo foo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        App.getComponent().inject(this);
        setContentView(R.layout.activity_main);

        TextView tv = (TextView)findViewById(R.id.textview);
        tv.setText(foo.toString());
    }
}

RoboDaggerComponent.java

package xx.robodagger;

import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules={RoboDaggerModule.class})
interface RoboDaggerComponent {

    void inject(MainActivity mainActivity);
}

RoboDaggerModule.java

package xx.robodagger;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@SuppressWarnings("unused")
@Module
class RoboDaggerModule {

    @Provides
    @Singleton
    Foo providesFoo() { return new Foo(); }
}

And now in the test directory,

FakeFoo.java

package xx.robodagger;

class FakeFoo extends Foo {

    @Override public String toString() {
        return "bar";
    }
}

MainActivityTest.java

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import javax.inject.Inject;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class MainActivityTest {

    TestRoboDaggerComponent testRoboDaggerComponent;

    @Inject Foo foo;

    @Before
    public void setup() {
        testRoboDaggerComponent = DaggerTestRoboDaggerComponent.builder()
            .testRoboDaggerModule(new TestRoboDaggerModule())
            .build();
        testRoboDaggerComponent.inject(this);
    }

    @Test
    public void fooBarTest() {
        assert(foo.toString().equals("bar"));
    }
}

TestRoboDaggerComponent.java

package xx.robodagger;

import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules={TestRoboDaggerModule.class})
interface TestRoboDaggerComponent {

    void inject(MainActivityTest mainActivityTest);
}

TestRoboDaggerModule.java

package xx.robodagger;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@SuppressWarnings("unused")
@Module
class TestRoboDaggerModule {

    @Provides
    @Singleton
    Foo providesFoo() { return new FakeFoo(); }
}
1
If you solved your problem yourself you should answer your own question and accept your own answer.David Medenjak
RoboDaggerModule and TestRoboDaggerModule are different classes though. How will .roboDaggerModule(new TestRoboDaggerModule()) work?dannyroa
Does TestRoboDaggerModule extend RoboDaggerModule?dannyroa
Yes, sorry for the omission. TestRoboDaggerModule extends RoboDaggerModule. You can add test specific items there.BeachJustice

1 Answers

2
votes

Delete TestRoboDaggerComponent.java, it's not needed

Modify App.java to setup the component via a protected method

@Override public void onCreate() {
    super.onCreate();
    setupComponent();
}

protected void setupComponent() {
    roboDaggerComponent = DaggerRoboDaggerComponent.builder()
            .roboDaggerModule(new RoboDaggerModule())
            .build();
}

Modify TestApp.java to extend App, and override the previously mentioned method with the TestModule

public class TestApp extends App {

    @Override
    protected void setupComponent() {
        roboDaggerComponent = DaggerRoboDaggerComponent.builder()
            .roboDaggerModule(new TestRoboDaggerModule())
            .build();
    }
}

And finally, in the actual test, leverage Robolectric's power to specify the Application module

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class,
        application = TestApp.class)
public class MainActivityTest {

    private MainActivity mainActivity;

    @Before
    public void setup() {
        mainActivity = Robolectric.setupActivity(MainActivity.class);
    }

    @Test
    public void fooBarTest() {
        TextView tv = (TextView)mainActivity.findViewById(R.id.textview);
        assert(tv.getText().equals("bar"));
    }
}

Notice that there isn't any DI in the actual test proper. The injection of MainActivity takes place as expected, and with the overridden module. If you have a dependency that uses @Injects in the constructor, the solution per Dagger's documentation is not to use injection in the test, but to just call the normal constructor with mocked objects. This all makes sense. I still think the originally reported issue is a bug, but whatever.