Mockito and Guice : mocking a @Provides method
Asked Answered
J

2

6

I have a module in my class called MainModule. It has various bindings in it, one of which is a binding to my custom service interface called HttpClientPool

public class MainModule extends AbstractVertxModule{
        binder.bind(HttpClientPool.class).to(HttpClientPoolImpl.class).in(Singleton.class);
        // other bindings
} 

@Provides @Singleton
    public TokenClient tokenClient(HttpClientPool clientPool){
        return new TokenClient(clientPool);
}

The module is also a provider of an object called tokenClient, as seen above.

The tokenClient is being injected somewhere else in a different class and a few methods are being invoked on this object.

In my unit tests, I'm intending to use Mockito to get a mocked tokenClient object. That means, I would like MainModule to provide a mocked object, instead of a real one. I have tried using a testMainModule that looks like this:

public class testMainModile implements Module{
    private TokenClient tokenClient;

    public TokenModule(TokenClient client) {
        this.tokenClient= client;
    }

    @Override
    public void configure(Binder binder) {
        binder.bind(TokenClient.class).toInstance(tokenClient);
    }
}

An excerpt from my unit tests:

@Mock
private TokenClient tokenClient;
// Stuff between
Injector messagingInjector = Guice.createInjector(new TestMainModule(tokenClient));
        mainModule = messagingInjector.getInstance(MainModule.class);

Somehow, all I get is a real object from the mainModule object. Am I missing something?

Jacintojack answered 12/12, 2014 at 22:54 Comment(3)
Why are you injecting MainModule? Instead you should be calling injector.getInstance(TokenClient.class) on an Injector you create with new MainModule() (production) or new TestMainModule() (tests). Arguably, your unit tests shouldn't use Guice injectors at all, but that advice still holds true for system or integration tests.Desperado
It does not make much sense to get a Module from an Injector.Kuhl
@JeffBowman Thanks for the insight. But How do I do that? I get a mocked tokenClient from the new TestMainModule();. How do I pass it on to a MainModule object being used in my tests?Jacintojack
C
5

I assume that you have a class that provides some functionality. This is the class you want to unit test. It requires a tokenClient that you inject into the class to work properly. So the question you are facing is: how does my class under test get a mocked tockenClient injected when I test it.

There are several possibilities ... possibly the easiest one is to strictly use constructor injection only and create the instances of your class under test via "new" and just hand them mocked instances created separately.

If you want to stick with guice, it is possible to override Bindings and Providers or even provide completely isolated test-modules.

I prefer using the needle4j framework (I am a contributor so I am biased), a dependency injection simulator that by default injects mocks unless configured to do otherwise. If used correctly (stick to one class unit, do not try to set up integration level tests), this might be the fastest and simpliest way to test classes depending on injected instances.

Choctaw answered 14/12, 2014 at 22:18 Comment(1)
Awesome, I was able to override the binding concerning tokenClient after looking at the page you linked. thanks!Jacintojack
P
3

The accepted answer suggests a number of approaches. If you need to override the binding (e.g. if you cannot modify MainModule), here is a full working example:

class MainModuleTest {
    @Mock
    @Bind
    TokenClient tokenClient;

    @Test
    public void testHelloWorldClient() {
        MockitoAnnotations.openMocks(this);

        Module testMainModule = Modules.override(new MainModule())
                .with(BoundFieldModule.of(this)
        
        ...
    }
}

Notes:

  • You will need to add a dependency on guice-testlib
  • With this approach, we can do away with the class TestMainModule

How it works:

  • BoundFieldModule.of(this) creates a test module on the fly, using all of the fields in the test class which are annotated with @Bind. Since tokenClient is also a mock instance, the binding in the module will contain the mock.
  • Modules.override will overlay the BoundFieldModule over MainModule. Concretely,
    • it will use the mock TokenClient, since it gives precedence to the binding in the BoundFieldModule; and
    • it will use the real HttpClientPool, since this is only bound in MainModule.

I'm not sure what the point is of getting the testMainModule instance. If we wanted to get an instance of HttpClientPool from the test module, we could do it like this:

class MainModuleTest {
    @Mock
    @Bind
    TokenClient tokenClient;

    @Inject
    HttpClientPool httpClientPool;

    @Test
    public void testHelloWorldClient() {
        MockitoAnnotations.openMocks(this);

        injector = Guice.createInjector(
            Modules.override(new MainModule()).with(BoundFieldModule.of(this))
        )
        injector.injectMembers(this)

        httpClientPool.doStuff()
    }
}

In this case, the injector uses the overridden module to inject an instance of HttpClientPoolImpl into the httpClientPool field.

Prodigal answered 25/5, 2022 at 10:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.