3

I want to use RoboGuice in a standard Android JUnit instrumentation test case and override one piece of my app's actual wiring with a mock for testing. I can't find anything online that explains how to do this as all of my search results go to Robolectric with RoboGuoice. I am not using Robolectric nor can I use it in my app for various reasons. Has anyone wired an app with RoboGuice and injected mocks for standard Android Intrumentation test cases?

Cliff
  • 10,586
  • 7
  • 61
  • 102
  • Did you find a solution to this? I want to do the same (mainly because I failed setting up Roboelectric) – dragi Aug 06 '14 at 13:42
  • I'm having the same issue with unit tests. Did you make any progress @helleye – amadib Aug 21 '15 at 23:58
  • This was over a year ago. I believe her you something from the answer below to help me. But I do not remember. – Cliff Aug 22 '15 at 00:08

2 Answers2

0

I'm using the Roboguice 3 and I solved this problem with the following setup and teardown methods within the standard ActivityInstrumentationTestCase2.

Obviously you would need to replace new TestModule() in the snippet below with your own test module class.

@Override
protected void setUp() throws Exception {
    super.setUp();
    Application app = (Application)getInstrumentation().getTargetContext()
                      .getApplicationContext();
    RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, 
                   Modules.override(RoboGuice.newDefaultRoboModule(app))
                   .with(new TestModule()));
    getActivity();
}

@Override
protected void tearDown() throws Exception {
    RoboGuice.Util.reset();
    super.tearDown();
}
Louth
  • 11,401
  • 5
  • 28
  • 37
  • I believe it's `RoboGuice.util.reset();` in the `tearDown()` – amadib Aug 21 '15 at 23:57
  • 1
    No it's not. There's an inner Util class within Roboguice. – Louth Aug 22 '15 at 01:46
  • You're right, for RoboGuice 3. I was trying this with RoboGuice 2. – amadib Aug 22 '15 at 19:08
  • Changing `RoboGuice.setBaseApplicationInjector()` to `RoboGuice.getOrCreateBaseApplicationInjector()` in my main app throws a `java.lang.NoClassDefFoundError: roboguice.RoboGuice` in my `onCreate()` any idea why? – amadib Aug 22 '15 at 20:36
0

I've managed to get it work in a simple usage way, you just bind dependencies inside rule using builder and may forget about them later, it will do everything by itself. You may think it's over engineered, but it's realy good for reusing if tyou have a many test classes with robo guice dependencies inside. Usage in test classes looks like:

@Rule
    public InjectWithMocksRule injectWithMocksRule = new InjectWithMocksRule(
            this,
            () -> new InjectRule
                    .BindingBuilder()
                    .add(MyClass.class, mockedClassImpl)
                    .add(SomeInterface.class, mockedInterfaceImpl));

I wrote helper class TestBindingModule:

public class TestBindingModule extends AbstractModule {

    private HashMap<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();

    @Override
    @SuppressWarnings("unchecked")
    protected void configure() {
        Set<Entry<Class<?>, Object>> entries = bindings.entrySet();
        for (Entry<Class<?>, Object> entry : entries) {
            bind((Class<Object>) entry.getKey()).toInstance(entry.getValue());
        }
    }

    public void addBinding(Class<?> type, Object object) {
        bindings.put(type, object);
    }

    public void addBindings(HashMap<Class<?>, Object> bindings) {
        this.bindings.putAll(bindings);
    }

    public static void setUp(Object testObject, TestBindingModule module) {
        Module roboGuiceModule = RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application);
        Module testModule = Modules.override(roboGuiceModule).with(module);
        RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, testModule);
        RoboInjector injector = RoboGuice.getInjector(RuntimeEnvironment.application);
        injector.injectMembers(testObject);
    }

    public static void tearDown() {
        Application app = RuntimeEnvironment.application;
        DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
        RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
    }

}

Than I use custom Rule to make it work easy:

public class InjectRule implements TestRule {

    public interface BindingBuilderFactory {
        BindingBuilder create();
    }

    public static class BindingBuilder {
        private HashMap<Class<?>, Object> bindings = new HashMap<>();

        public BindingBuilder add(Class<?> dependencyClass, Object implementation) {
            bindings.put(dependencyClass, implementation);
            return this;
        }

        HashMap<Class<?>, Object> buildBindings() {
            return this.bindings;
        }
    }

    private Object target;
    private BindingBuilderFactory bindingBuilderFactory;

    public InjectRule(Object target, BindingBuilderFactory bindingBuilderFactory) {
        this.target = target;
        this.bindingBuilderFactory = bindingBuilderFactory;
    }

    private void overrideTestInjections(Object target) {
        TestBindingModule module = new TestBindingModule();
        module.addBindings(this.bindingBuilderFactory.create().buildBindings());
        TestBindingModule.setUp(target, module);
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new StatementDecorator(base);
    }


    private class StatementDecorator extends Statement {

        private Statement baseStatement;

        StatementDecorator(Statement b) {
            baseStatement = b;
        }

        @Override
        public void evaluate() throws Throwable {
            before();
            try {
                baseStatement.evaluate();
            } catch (Error e) {
                throw e;
            } finally {
                after();
            }
        }

        void after() {
            TestBindingModule.tearDown();
        }

        void before() {
            overrideTestInjections(target);
        }
    }

}

Also you may want to init mocks with @Mock annotation inside of your test classes, so you need another custom rule:

public class MockitoInitializerRule implements TestRule {

    private Object target;

    public MockitoInitializerRule(Object target) {
        this.target = target;
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new MockitoInitializationStatement(base, target);
    }

    private class MockitoInitializationStatement extends Statement {
        private final Statement base;
        private Object test;

        MockitoInitializationStatement(Statement base, Object test) {
            this.base = base;
            this.test = test;
        }

        @Override
        public void evaluate() throws Throwable {
            MockitoAnnotations.initMocks(test);
            base.evaluate();
        }
    }
}

And, finaly, you want to combine them to mock mocks first and then set them as dependencies:

public class InjectWithMocksRule implements TestRule {

    private final RuleChain delegate;

    public InjectWithMocksRule(Object target, InjectRule.BindingBuilderFactory bindingBuilderFactory) {
        delegate = RuleChain
                .outerRule(new MockitoInitializerRule(target))
                .around(new InjectRule(target, bindingBuilderFactory));
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return delegate.apply(base, description);
    }
}
A. Shevchuk
  • 2,109
  • 14
  • 23