VictorGram
VictorGram

Reputation: 2661

Calling real method using Mockito

I am using JDK 11 and spring boot. I am implementing a rest API and have 3 layers:

I had classes against interfaces at the data-access-layer and did not have any interface at the service layer.

I wrote integration tests using MockMvc, Mockito, etc to exercise the whole path for each point, exposed by the controller. This was not a problem until I tried to introduce the interface at the service layer.

Initially, I mocked only repositories/Daos. So the class structure looked like:

    public interface ClientRepo{
    ......
    }

    public class ClientRepoImpl implements ClientRepo{
    ......
    }

Mocked the returned data as:

    @MockBean 
    private ClientRepo client;

    ....
    Mockito.when(client.isExistFkUnitId(Mockito.any(UUID.class))).thenReturn(false); 

Everything was fine so far.

Now I have introduced interface at the service layer as :

    public interface ClientService{
    ......
    }

    public class ClientServiceImpl implements ClientService{
    ......
    }

And tried ( Trying to call actual service method):

    @MockBean 
    private ClientService clientService;

    ....
    Mockito.when(clientService.isExistFkUnitId(Mockito.any())).thenCallRealMethod();

But getting nothing but null all the time.

Is there a way to make the real method call keeping the interface?

Upvotes: 2

Views: 20252

Answers (4)

Steven Dewey
Steven Dewey

Reputation: 61

I was having the same problem. My problem was due to the ClientService having dependencies that were not mocked when I set up the tests in this format. So ClientService had a mock, but if I tried clientService.productService.get() or something of that nature the dependant productService was always null. I solved this using testing reflection:

    @MockBean
    DependentService mockDependentService
    
    ControllerToTest controllerToTest

    @BeforeEach
    public void setup() {
       mockDependentService = mock(DependentService.class);
       controllerToTest = mock(ControllerToTest.class);
       ReflectionTestUtils.setField(controllerToTest, "dependantService", mockDependentService);
    }
    
    @Test
    void test() {
        //set up test and other mocks 
        //be sure to implement the below code that will call the real method that you are wanting to test
        when(controllerToTest.methodToTest()).thenCallRealMethod();
        //assertions
    }

Note that "dependantService" needs to match whatever you have named the instance of the service on your controller. If that doesn't match the reflection will not find it and inject the mock for you.

This approach allows all the methods on the controller to be mocked by default, then you can specifically call out which method you want to use the real one. Then use the reflection to set any dependencies needed with the respective mock objects.

Hope this helps!

Upvotes: 0

rduque
rduque

Reputation: 359

As you are mocking an interface Mockito doesn't know which implementation are you referring. The only way will be to use the Class.

Upvotes: 0

pamcevoy
pamcevoy

Reputation: 1246

I think you want to use @Spy annotation instead of @Mock annotation on the field where you want to call the real method. I don't happen to have an example to verify this though. https://javadoc.io/doc/org.mockito/mockito-core/2.21.0/org/mockito/Spy.html

Then you can do doCallRealMethod().when(clientService.isExistFkUnitId(Mockito.any())). Because with a spy object you call doReturn/when instead of when/doReturn. https://javadoc.io/doc/org.mockito/mockito-core/2.21.0/org/mockito/Mockito.html#do_family_methods_stubs

Upvotes: 7

Johannes Klug
Johannes Klug

Reputation: 1115

Well, there is no "real" method to call. (Ignoring the fact that default methods in interfaces are a thing nowadays)

Generally, unit tests should be written for the target class in an isolated fashion. Like this, you are always "testing" the "isExistFkUnitId" method as well. You could set the mock up for specific values:

Mockito.when(clientService.isExistFkUnitId("valueA").thenReturn("answerA");
Mockito.when(clientService.isExistFkUnitId("valueB").thenReturn("answerB");

Anyways... to respond to your actual question:

If possible, you can instantiate the implementation in a way that the desired method is working and call it through the mock:

ClientServiceImpl clientServiceImpl = new ClientServiceImpl(...);
// spaghetti code only for demonstration purposes ;)
Mockito.when(clientService.isExistFkUnitId(Mockito.any())).then(i -> clientServiceImpl.isExistFkUnitId((String) i.getArguments()[0]));

POC test:

@Test
public void testit() {
    Myclass myclass = new Myclass();
    Myinterface mock = Mockito.mock(Myinterface.class);
    Mockito.when(mock.myMethod(Mockito.any())).then(i -> myclass.myMethod((String) i.getArguments()[0]));

    assertThat(mock.myMethod(" works")).isEqualTo("yeehaa works");
}

public interface Myinterface {
    String myMethod(String params);
}

public static class Myclass implements Myinterface {
    @Override
    public String myMethod(String params) {
        return "yeehaa" + params;
    }
}

Not exactly a beautiful solution, but if there is no way around it, it should work.

Upvotes: 1

Related Questions