Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito and Guice : mocking a @Provides method

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?

like image 576
Sasanka Panguluri Avatar asked Sep 30 '22 05:09

Sasanka Panguluri


2 Answers

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.

like image 123
Jan Galinski Avatar answered Oct 27 '22 10:10

Jan Galinski


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.

like image 31
rusheb Avatar answered Oct 27 '22 09:10

rusheb