Overriding Binding in Guice
Asked Answered
H

5

161

I've just started playing with Guice, and a use-case I can think of is that in a test I just want to override a single binding. I think I'd like to use the rest of the production level bindings to ensure everything is setup correctly and to avoid duplication.

So imagine I have the following Module

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}

And in my test I only want to override InterfaceC, while keeping InterfaceA and InterfaceB in tact, so I'd want something like:

Module testModule = new Module() {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(new ProductionModule(), testModule);

I've also tried the following, with no luck:

Module testModule = new ProductionModule() {
    public void configure(Binder binder) {
        super.configure(binder);
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(testModule);

Does anyone know if it's possible to do what I want or am I completely barking up the wrong tree??

--- Follow up: It would seem I can achieve what I want if I make use of the @ImplementedBy tag on the interface and then just provide a binding in the test case, which works nicely when there is a 1-1 mapping between the interface and implementation.

Also, after discussing this with a colleague it would seem we'd head down the road of overriding an entire module and ensuring we have our modules defined correctly. This seems like it might cause a problem though where a binding is misplaced in a module and needs to be moved, thus possibly breaking a load of tests as bindings may no longer be available to be overriden.

Heffernan answered 27/1, 2009 at 11:42 Comment(0)
T
173

This might not be the answer you're looking for, but if you're writing unit tests, you probably shouldn't be using an injector and rather be injecting mock or fake objects by hand.

On the other hand, if you really want to replace a single binding, you could use Modules.override(..):

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}
public class TestModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));

See details in the Modules documentation.

But as the javadoc for Modules.overrides(..) recommends, you should design your modules in such a way that you don't need to override bindings. In the example you gave, you could accomplish that by moving the binding of InterfaceC to a separate module.

Tzar answered 10/2, 2009 at 5:4 Comment(6)
Thanks Albert, that gets me someway down the road towards doing what I want. That's in a production release yet tho! And this is for integration tests, not unit tests, which I why I want to ensure everything else is being built correctlyHeffernan
I've added a concrete example to the code. Does it get you further along?Tzar
Unless I'm mistaken, ovveride looses the proper Stage while doing so (i.e. DEVELOPMENT is systematically used).Duke
>>>but if you're writing unit tests, you probably shouldn't be using an injector and rather be injecting mock or fake objects by hand.>>> I do not understand that. The all idea of encapsulating creation is to keep it in one place. If one inject by hand rather than by an injector, then if the constructor change you have a problem, you need to propagate that i all the test class that do the creation by hand.Consultation
Size matters. When your dependency graph grows, wiring up by hand can be quite a pain. Also when wiring changes you need to manually update all your manual wiring places. Overriding allows you to handle that automatically.Obliging
@Duke That is a bug in Guice 3 that I fixed for Guice 4.Fulllength
R
13

Why not to use inheritance? You can override your specific bindings in overrideMe method, leaving shared implementations in configure method.

public class DevModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(TestDevImplA.class);
        overrideMe(binder);
    }

    protected void overrideMe(Binder binder){
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
};

public class TestModule extends DevModule {
    @Override
    public void overrideMe(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}

And finally create your injector this way:

Guice.createInjector(new TestModule());
Riebling answered 27/8, 2014 at 13:19 Comment(1)
The @Override doesn't seem to work. Especially if it's done on a method that @Provides something.Hylozoism
M
6

If you don't want to change your production module and if you have a default maven-like project structure like

src/test/java/...
src/main/java/...

You can just create a new class ConcreteC in your test directory using the same package as for your original class. Guice will then bind InterfaceC to ConcreteC from your test directory whereas all other interfaces will be bound to your production classes.

Male answered 6/1, 2015 at 16:34 Comment(0)
C
1

You want to use Juckito where you can declare your custom configuration for each test class.

@RunWith(JukitoRunner.class)
class LogicTest {
    public static class Module extends JukitoModule {

        @Override
        protected void configureTest() {
            bind(InterfaceC.class).to(MockC.class);
        }
    }

    @Inject
    private InterfaceC logic;

    @Test
    public testLogicUsingMock() {
        logic.foo();
    }
}
Concubine answered 11/11, 2014 at 11:35 Comment(0)
F
1

In a different setup, we have more than one activity defined in separate modules. The activity that's being injected into is in an Android Library Module, with its own RoboGuice module definition in the AndroidManifest.xml file.

The setup looks like this. In the Library Module there are these definitions:

AndroidManifest.xml:

<application android:allowBackup="true">
    <activity android:name="com.example.SomeActivity/>
    <meta-data
        android:name="roboguice.modules"
        android:value="com.example.MainModule" />
</application>

Then we have a type being injected:

interface Foo { }

Some default implementation of Foo:

class FooThing implements Foo { }

MainModule configures the FooThing implementation for Foo:

public class MainModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Foo.class).to(FooThing.class);
    }
}

And finally, an Activity that consumes Foo:

public class SomeActivity extends RoboActivity {
    @Inject
    private Foo foo;
}

In the consuming Android Application Module, we would like to use SomeActivity but, for testing purposes, inject our own Foo.

public class SomeOtherActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

One might argue to expose the module handling to the client application, however, we need to mostly hide the components being injected because the Library Module is an SDK, and exposing pieces has larger implications.

(Remember, this is for testing, so we know the internals of SomeActivity, and know it consumes a (package visible) Foo).

The way I found that works makes sense; use the the suggested override for testing:

public class SomeOtherActivity extends Activity {
    private class OverrideModule
            extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).to(OtherFooThing.class);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RoboGuice.overrideApplicationInjector(
                getApplication(),
                RoboGuice.newDefaultRoboModule(getApplication()),
                Modules
                        .override(new MainModule())
                        .with(new OverrideModule()));
    }

    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Now, when SomeActivity is started, it will get OtherFooThing for its injected Foo instance.

It's a very specific situation where, in our case, OtherFooThing was used internally to record test situations, while FooThing was used, by default, for all other uses.

Keep in mind, we are using #newDefaultRoboModule in our unit tests, and it works flawlessly.

Frumentaceous answered 27/4, 2015 at 17:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.