Injecting mock objects

58 views
Skip to first unread message

Rémy Sanchez

unread,
Sep 20, 2013, 4:22:52 AM9/20/13
to sil...@googlegroups.com
Hi all,

I must be somewhat dumb, but still I don't see any obvious way to change implementations depending on what you are doing.

I mean, if I'm turning to DI it's really not for the fun of getting syntaxic sugar everywhere, but rather because I actually have different implementations for my services.

For instance, I did the following code:

public class LoveMachine {
private final Injector injector = Bootstrap.injector(RootModule.class);
private final int answer = injector.resolve(Dependency.dependency(int.class));
public int getAnswer() {
return answer;
}
}

With the very simple test as follows:

@Test
public void testGetAnswer() throws Exception {
LoveMachine loveMachine = new LoveMachine();
assertEquals(42, loveMachine.getAnswer());
}

Now, I have a mock implementation for that answer, in the following subclass of my test:

public class MyAnswer extends BinderModule {
@Override
protected void declare() {
bind(int.class).to(OVERRIDDEN_ANSWER);
}
}

How can I possibly replace the root module or whatever? Should I create a RootBundle that holds the logic of loading specific modules depending on some configuration? Wouldn't that mean including in my main code some logic only useful for tests?

Thanks!

jan

unread,
Sep 21, 2013, 6:59:35 AM9/21/13
to sil...@googlegroups.com
Hi Rémy,

your example looks quite strange to me as you are using the Injector somewhat manually. Maybe you want to illustrate the example but maybe you also got wrong how to use the Injector effectively.

In case of your LoveMachine I would have expected this (code like without DI)

class LoveMachine {
 
int answer;
 
LoveMachine(int answer) { this.answer = answer; }

 
int getAnswer() { return answer; }
}

Than binding it like

protected void declare() {
  bind(LoveMachine.class).toConstructor();
  bind
(int.class).to(42);
}

Also remember that int is a fairly common class so you might want to use this bind instead of the untargeted one above

injectingInto(LoveMchine.class).bind(int.class).to(42);

In case of a test that should use another value/instance for that int I think the best is to create a TestRootBundle on top of your normal root. 
class TestRootBundle extends BootstrapperBundle {
  bootstrap
() {
    install
(RootBundle.class);
    uninstall
(UsualModuleX.class);
    install
(TestModuleX.class);
   
//...
 
}
}
This approach requires that the part you want to "override" is isolated in a Bundle or Module so that you can un-install it and install a Bundle or Module that does the same setup for test. 
An alternative to this is to do a more specific binding but that doesn't seam very clean in the long run. 

Beside this most general way of composing a configuration differently you can use 
With any of those you use the same RootBundle in both test and usual setup but chose another edition/feature, option or configuration. 

Picking a specific Module based on scenario (as you describe it) is what edition/features and options or another root bundle on top are used for. 
You should not create if-else logic within any of your modules! While this seams easy to do it will mess you up in the long run (except you have a toy sized project).
Instead hard-code or compose a Globals object (with Options and Edition) and bootstrap the one tree of Bundles and Modules you use for everything. 
The chosen option/edition or feature set will take care of including the right composition of bindings.

Cheers

Jan

jan

unread,
Sep 21, 2013, 7:35:43 AM9/21/13
to sil...@googlegroups.com
I came to think about yet another way to handle similar cases. You can use asDefault() (described a bit here  http://www.silkdi.com/userguide/binds.html#require ) to bind the usual value for the answer

asDefault().bind(int.class).to(42);

Than in another module you are allowed to do another bind such as

bind(int.class).to(23);

While this is the closest that behaves like overrides I really recommend to get away from the idea of overriding bindings as well as overriding in general. An override requires you to look at/think about the overriding and the overridden artefact. Thereby this technique quickly stacks up to a mental model with many branches and layers that you will not be capable of thinking through - you'll build and miss nasty corner cases.
The features of Silk should allow you to compose flexible without actual overriding something. An artefact is present or not present but never partially present what greatly simplifies the mental model.

Cheers
Jan 

Rémy Sanchez

unread,
Sep 23, 2013, 9:43:29 AM9/23/13
to sil...@googlegroups.com
Hi Jan,

Thank you very much for you reply, I actually didn't get the usefulness of the .toConstructor() binding. This is actually awesome :) I kind of like the no magic world...

The case of int is of course some test, I don't think I'll ever inject an integer like this, or at least it will be scoped as you say.

This way I understand better how you can organize your modules and so on based on the idea that you bind constructors. Maybe a small toy project to show everything in one piece would help idiots like me ;)

Regarding options though, is there any way you can set up orthogonal options ? For instance, I have a live or a batch mode, running in different client integrations. So I would want to kind of do something like set "LiveMode + ClientA", if you see what I mean.

Cheers,
Rémy

jan

unread,
Sep 24, 2013, 2:52:25 AM9/24/13
to sil...@googlegroups.com
Hi,

yes the "no magic world" is nice - once you get the basics its way easier to understand how things work and what happens.

I maybe did no emphasize enough that when you think about a Injector context (the bindings) think about a sorted list, more specific comes first. first matching binding is picked. That's all there is to know and understand what binding effectively will "win".

But back to your question: I think I understand what you plan to do. Options should be the weapon of choice.

It sounds you should have a Mode enum and a Client enum. 
Than you create one Module per Client constant and one Module per Mode constant. 
These modules declare the bindings specific to the client or mode. You can also think of using a "fallback" Module in addition that uses the asDefault() to setup a standard configuration. 
If Mode and Client intent to bind the same instances (they are not fully orthogonal) one of them could maybe use the asDefault() so the other can adapt that further.

With this present you just chose the actual Mode and Client when bootstrapping:

Options options = Options.STANDARD.chosen( Mode.Live ).chosen( Client.A );
Globals globals = Globals.STANDARD.options( options );
Injector injector = Bootstrap.injector( RootBundle.class, globals );

/Jan

Rémy Sanchez

unread,
Sep 24, 2013, 4:16:02 AM9/24/13
to sil...@googlegroups.com
Oh well, I see. Looks good :)

Thanks!

jan

unread,
Sep 24, 2013, 9:00:43 AM9/24/13
to sil...@googlegroups.com
Oh....I forgot about one more thing that might be what you want.

Preset modules (BinderModuleWith) allow to pass down a value into modules so that you can do bind to values that are passed into the module.
Look here for an example


For example to have some Properties available:

private static class PresetModuleBindsModule1 extends BinderModuleWith<Properties> {
  @Override
   protected void declare( Properties preset ) {
     bind( named( "foo" ), String.class ).to( preset.getProperty( "foo.text" ) );
  }
}

The actual value for a Presets is set per type when bootstrapping (like with Options)

Properties props = new Properties();
props.put( "foo.text", "bar" );
Presets
presets = Presets.EMPTY.preset( Properties.class, props );
Injector injector = Bootstrap.injector( RootBundle.class, Globals.STANDARD.presets( presets ) );

Cheers
/Jan

Rémy Sanchez

unread,
Sep 24, 2013, 10:44:02 AM9/24/13
to sil...@googlegroups.com
Hum, interesting, although I'm not sure to need it.

Another thing however, is there any prefered way for the user to set the values of options ? The run mode depends on the main() you use, but the client is simply a config options.

I'd put it in the configuration file, however this would give me a chick & egg problem if I want to get the configuration from DI :/

jan

unread,
Sep 25, 2013, 5:18:13 AM9/25/13
to sil...@googlegroups.com
It really sounds like you were overcomplicating you setup code.

There should be usually these stages
  • manually setup (e.g. of DI container)
  • bootstrapping DI container
  • running the thing
It sounds you mix up the first 2. If there are config options e.g. provided by a properties file you prepare this in setup stage and you might pass this to DI if it is needed there as well. But the setup stage itself should not need the DI container. This is the bit of manual code that glues the inputs and configs in form of files and whatever else to the bootstrapping and starting stage. If you setup is that complicated that you feel need of using DI you should really try to simplify this. If it is just that complicated it might help to use another DI container bootstrapped for the setup stage, that is just an input for the actual bootstrapping of the application. This container is just GC'd directly after the setup stage.

/Jan
Reply all
Reply to author
Forward
0 new messages