6

I am trying to use Dagger in an Android functional test which inherits ActivityInstrumentationTestCase2.

The setup code looks like this:

@Override
protected void setUp() {
    // TODO Auto-generated method stub
    try {
        super.setUp();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    ObjectGraph.create(new TestModule()).inject(this);
    this.activity = super.getActivity();
}`

The OnCreate method, which is fired by calling super.getActivity(), does not use the classes provided by the TestModule. But if I run my activity manually (outside of the testing context) then all the appropriate classes are provided/injected by my non-test module.

Christian Gruber
  • 4,691
  • 1
  • 28
  • 28
Steven Mark Ford
  • 3,372
  • 21
  • 32
  • So it's not easy to see here what is going on, because we don't see what you're injecting into the test, etc. Can you trim and provide a simplified example of your production and test modules, that replicates this issue? – Christian Gruber Jan 16 '13 at 15:58
  • Maybe my answer in this related post helps you: http://stackoverflow.com/questions/15630589/android-functional-testing-with-dagger?rq=1 – IIIRepublica May 14 '13 at 10:35

2 Answers2

3

I found a way to use Dagger with ActivityInstrumentationTestCase2 by lazily creating the Object Graph. What I do is wait to create the Object Graph until the very first time a class wants to be injected, so as long as you add your modules before calling getActivity() (which starts the activity lifecycle for the activity under test) and use overrides = true in your test modules, this will work. Here's the relevant classes and snippets:

GraphHolder, as the name implies, holds the ObjectGraph object for us. We will make all calls to this class rather than directly to ObjectGraph.

public class GraphHolder {

    private static GraphHolder sInstance;

    private Object[] mModules;
    private ObjectGraph mGraph;

    private GraphHolder() {
    }

    public static GraphHolder getInstance() {
        if (sInstance == null) {
            sInstance = new GraphHolder();
        }

        return sInstance;
    }

    public void inject(Object object) {
        if (mGraph == null) {
            create();
        }

        mGraph.inject(object);
    }

    public <T> T get(Class<T> type) {
        if (mGraph == null) {
            create();
        }

        return mGraph.get(type);
    }

    public void addModules(Object... modules) {
        if (mGraph != null) {
            mGraph.plus(modules);
        } else {
            if (mModules == null) {
                mModules = modules;
            } else {
                mModules = concatenate(mModules, modules);
            }
        }
    }

    private void create() {
        mGraph = ObjectGraph.create(mModules);
        mModules = null;
    }

    private Object[] concatenate(Object[] a, Object[] b) {
        int aLength = a.length;
        int bLength = b.length;

        Object[] c = new Object[aLength + bLength];
        System.arraycopy(a, 0, c, 0, aLength);
        System.arraycopy(b, 0, c, aLength, bLength);

        return c;
    }
}

We'll add our modules in the Application class:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        GraphHolder.getInstance().addModules(getModules());
    }

    Object[] getModules() {
        return new Object[]{
                // your modules here
        };
    }
}

Inside the classes we want to inject, we'll simply call GraphHolder.getInstance().inject(this) rather than ObjectGraph.inject(this)

In our test modules, we'll provide the objects we want to override for testing and add overrides = true to the @Module annotation. This tells the object graph to prefer this module's providers over others if there's a conflict.

Then, in our tests:

@Inject Foo mFoo;

@Override
public void setUp() {
    super.setUp();
    GraphHolder.getInstance().addModules(new TestFooModule());
    GraphHolder.getInstance().inject(this); // This is when the object graph will be created
}
Jason Robinson
  • 31,005
  • 19
  • 77
  • 131
1

ObjectGraph.create(new TestModule()).inject(this);

This code is trying to inject dependencies created by TestModule into your TestCase instead of the tested Activity. What you'd have to do here is

ObjectGraph.create(new TestModule()).inject(this.activity);

pstobiecki
  • 1,804
  • 1
  • 17
  • 30
  • 3
    you can't inject into an Activity from ActivityInstrumentationTestCase2 though because you have to call getActivity() to get a reference to the activity, and that will create it (calling onCreate automatically). Thus, if you do your inject in the onCreate method it will be too late to inject dependencies if you try to set this up from ActivityInstrumentationTestCase2. IMO this is a huge flaw in ActivityInstrumentationTestCase2. You can instead use ActivityUnitTestCase. – Matt Wolfe Nov 21 '13 at 21:55