6

Consider the following code:

@Singleton
public class MyServiceImpl {
    public int doSomething() {
        return 5;
    }
}

@ImplementedBy(MyServiceImpl.class)
public interface MyService {
    public int doSomething();
}

public class MyCommand {
    @Inject private MyService service;

    public boolean executeSomething() {
        return service.doSomething() > 0;
    }
}

public class MyCommandTest {
    @InjectMocks MyServiceImpl serviceMock;
    private MyCommand command;

    @Before public void beforeEach() {
        MockitoAnnotations.initMocks(this);
        command = new MyCommand();
        when(serviceMock.doSomething()).thenReturn(-1); // <- Error here
    }

    @Test public void mockInjected() {
        boolean result = command.executeSomething();
        verify(serviceMock).doSomething();
        assertThat(result, equalTo(false));
    }
}

My test is falling over when I attempt to stub the doSomething() method on my mock implementation object. I get the error:

org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'. For example: when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because: 1. you stub either of: final/private/equals()/hashCode() methods. Those methods cannot be stubbed/verified. Mocking methods declared on non-public parent classes is not supported. 2. inside when() you don't call method on mock but on some other object.

I am new to dependency injection via Guice, and am not sure why I cannot mock the implementation object in this way?

Marcus MacWilliam
  • 602
  • 1
  • 6
  • 24
  • are you missing a closing bracket after the doSomething call? i.e. when(serviceMock.doSomething()).thenReturn(-1); – AdamPillingTech Dec 21 '17 at 13:45
  • Sorry, yes. That was a typo. The code is an abstraction of the code I am trying to test, the above code may not compile, but shows the basics of how I am attempting to achieve my test. – Marcus MacWilliam Dec 21 '17 at 13:48
  • The error is not a compiler error, it is a runtime error from Mockito. – Marcus MacWilliam Dec 21 '17 at 13:49
  • try adding @RunWith(MockitoJUnitRunner.class) above the class definition – AdamPillingTech Dec 21 '17 at 13:50
  • 1
    actually you need to replace InjectMocks with Mock – AdamPillingTech Dec 21 '17 at 13:53
  • That successfully mocks the object. Thanks. But how do I now inject that into the class being tested? At the moment the test fails, as the actual service is being used, so doSomething() was not called via the mocked object, in the command, and the executeSomething() returns true. – Marcus MacWilliam Dec 21 '17 at 13:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161734/discussion-between-marcus-macwilliam-and-pillhead). – Marcus MacWilliam Dec 21 '17 at 14:00
  • unfortunately I can't access the chat page. try something like this... Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(MyServiceImpl.class).toInstance(serviceMock); } }); injector.injectMembers(command); – AdamPillingTech Dec 21 '17 at 14:10
  • Using the above appears to interfere with every other injector in our system, by removing all existing bindings, rather than just overriding the one being tested. – Marcus MacWilliam Dec 21 '17 at 14:44
  • without being able to see the rest of the code that you're using for your tests I can't tell what the best approach is but you'll need to bind your service mock and ask Guice to inject it. I was assuming that this was a unit test and therefore the scope was just the classes you have mentioned – AdamPillingTech Dec 21 '17 at 14:50
  • Perhaps not an exact duplicate, but this one is pretty close: https://stackoverflow.com/questions/27604727/how-to-init-guice-if-my-running-class-is-junit/27609674#27609674 – Daniel Pryden Dec 22 '17 at 02:39

1 Answers1

12

Test without CDI

A simple solution is to combine CDI with Constructor Injection, and forget about Guice for the test:

public class MyCommand {
    private final MyService service;

    @Inject
    public MyCommand(MyService service) {
        this.service = service;
    }

    public boolean executeSomething() {
        return service.doSomething() > 0;
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyCommandTest {
    @Mock
    MyServiceImpl serviceMock;
    private MyCommand command;

    @Before public void beforeEach() {
        MockitoAnnotations.initMocks(this);
        when(serviceMock.doSomething()).thenReturn(-1); // <- Error here

        // inject without Guice
        command = new MyCommand(serviceMock);
    }
}

Test with Mockito doing CDI

Else, if you do not like Constructor Injection, the test code should look like this:

@RunWith(MockitoJUnitRunner.class)
public class MyCommandTest {
    @Mock
    MyServiceImpl serviceMock;
    @InjectMocks 
    private MyCommand command;

    private AutoCloseable mockHandler;

    @Before
    public void beforeEach() {
        // initialize members annotated with @Mock and @InjectMocks
        mockHandler = MockitoAnnotations.openMocks(this);

        when(serviceMock.doSomething()).thenReturn(-1); // <- Error here
    }

    @After
    public void afterEach() throws Exception {
        mockHandler.close();
    }
}
tkruse
  • 10,222
  • 7
  • 53
  • 80
  • simple and effective :) – TriCore Nov 08 '21 at 04:41
  • apparently for JUnit5 `MockitoAnnotations::initMocks` is deprecated in favor of `MockitoAnnotations::openMocks` – Dexter Legaspi Jan 24 '22 at 00:50
  • @DexterLegaspi: Fixed. Thanks. This is the case for mockito3, not strictly related to junit5. See https://stackoverflow.com/questions/15494926/initialising-mock-objects-mockito. In general, feel free to improve answers by editing directly. – tkruse Jan 24 '22 at 02:26