0

I'm fairly new to Mocking, but I've read a lot, also took a course in udemy (not like I'm an expert, very far from it, just saying I've been studying this), but still, I cannot figure out something.

I have read this post Mockito - difference between doReturn() and when(), which made me understand a few things.

Now let me explain my problem.

I have a service impl class, that calls a repository class to get/do whatever I want. This service class is the one I'm writing my mockito's tests for.

This problem can be addressed in 2 different ways.

First way: (when thenReturn)

I have my test class which looks something like this (pseudo code):

serviceImplCastTest {
    @Spy
    @InjectMocks
    ServiceImplClass serviceImplClass;

    @Mock
    RepositoryClass repositoryClass;

    @Test
    void whenMethodInService_thenReturnNonEmptyMap() {
        List<Class> classList = new ArrayList<~>();
        Class class = new Class("Whatever");
        classList.Add(class)

        Map<String, Class> classMap = classList.stream.collect(Collectors.toMap(Class::getMethod, Function.identity()));

        when(serviceImplClass.methodInService()).thenReturn(classMap);

        Map<String, Class> actualMap = serviceImplClass.methodInService();

        assertFalse(actualMap.isEmpty());
        verify(repositoryClass, times(1)).methodInRepository();
}

So, this doesn't work because the method in the service, which returns a map, calls a method in the repository that returns a List of Class and then my method in the service turns that list into a map, and for some reason, Mockito is expecting a List of Class, instead of a Map. I get the Mockito wrong type of return error.

(This code works perfectly if the service method just returns the same type of List that the repository returns.)

Second way: (doReturn when)

serviceImplCastTest {
    @Spy
    @InjectMocks
    ServiceImplClass serviceImplClass;

    @Mock
    RepositoryClass repositoryClass;

    @Test
    void whenMethodInService_thenReturnNonEmptyMap() {
        List<Class> classList = new ArrayList<~>();
        Class class = new Class("Whatever");
        classList.Add(class)

        Map<String, Class> classMap = classList.stream.collect(Collectors.toMap(Class::getMethod, Function.identity()));

        doReturn(classMap).when(serviceImplClass).methodInService();

        Map<String, Class> actualMap = serviceImplClass.methodInService();

        assertFalse(actualMap.isEmpty());
        verify(repositoryClass, times(1)).methodInRepository();
}

Now, when using doReturn when, everything works fine, except the verify, repositoryClass.methodInRepository() doesn't get called, or at least is not spied properly or something, Mockito throws error "Wanted but not invoked".

So I'm not sure how to fix this on the Mockito side, because a simple way is to change logic, service return list instead of map, then change my application to receive list and map it over there, but this implies affecting logic, which I was forbidden to do so.

Also and I'm honestly interested on being able to mock this, because I have a few other equivalent cases, where my service class processes the list then turns it into something else, and mocking still expects a list to be returned.

How can I mock this? What is the correct approach?

Lauro182
  • 1,597
  • 3
  • 15
  • 41

1 Answers1

2

You are making the same mistake in both of your code snippets, in that you're stubbing a method in the class that you're trying to test, instead of stubbing the method in the mocked class.

This means that you're effectively just testing the stubbing, not testing the class itself.

What you want to do is

  1. Get rid of the Spy annotation - you don't need a spy for this.
  2. Stub the method of the repository class, not the service class.

That way, you'll be testing that the service class calls the repository class correctly, and handles the response from the repository class correctly. But you'll also be circumventing the implementation of the repository class, which is the whole point of using a mock.

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
  • You will also need a @Before setup() method to init / openMocks() and inject the repository mock. If the injection doesn't work and there is no setter than can be used, the mocked repository can be set using reflection or some ReflectionUtils if available. – Juan Feb 02 '22 at 00:43
  • "This means that you're effectively just testing the stubbing, not testing the class itself.". This line clarifies so much for me, I have been wondering all this time how am I testing anything if I'm telling my method what to return when it gets called. I was thinking that this level of testing was like more superficial, like just testing return types and that sort of things, but I was wrong, it always felt wrong. THANK YOU very much, this solves so much more than just the errors I got thrown by mockito, thank you, thank you! – Lauro182 Feb 02 '22 at 00:43