Stubbing void method of spybean failing

48 Views Asked by At

I have a void method bar in a service Foo as shown:

@Component
@EnableAsync
@AllArgsConstructor
public class Foo {
    private final SomeOtherService someOtherService;
    private final KafkaListenerEndpointRegistry registry;
    private final FooConfig fooConfig;

    @Async("myExecutor")
    public void bar(A a, B b, ConcurrentSkipListMap<Long, String> c, int d, int e) {
        someOtherService.doSomething(a, b, c, d, e);
    }
}

I am using Foo in MyService class,

@Service
@AllArgsConstructor
public class MyService {
    private final Foo foo;
        
    public void execute() {
    // call foo.bar();
    }
}

Now, I am trying to write a unit test for a case when calling foo.bar() can throw a RuntimeException. Here is my test class:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;

import java.util.concurrent.ConcurrentSkipListMap;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SpringBootMain.class},
            initializers = {ConfigDataApplicationContextInitializer.class})
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
public class MyServiceTest {

    @MockBean
    private SomeOtherService someOtherService;

    @SpyBean
    @Autowired
    private Foo foo;

    @Test
    public void test() {
        MyService myService = new MyService(foo);
        Mockito.doThrow(new RuntimeException("failed")).when(foo).bar(any(A.class), any(B.class), any(ConcurrentSkipListMap.class), anyInt(), anyInt());
        myService.execute();
        // some assertions
    }
}

But I am getting this error while running the test:

WARN org.springframework.test.context.TestContextManager -- 
Caught exception while invoking 'afterTestMethod' callback on TestExecutionListener [org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener] 
for test method [public void MyServiceTest.test() throws com.fasterxml.jackson.core.JsonProcessingException] 
and test instance [MyServiceTest@60fc4450]
org.mockito.exceptions.misusing.UnfinishedStubbingException: Unfinished stubbing detected

E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, which is not supported
 3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
1 matchers expected, 5 recorded:

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(any(), eq("String by matcher"));

For more info see javadoc for Matchers class.

I have tried other configurations as well including:

Mockito.doThrow(new RuntimeException("failed")).when(foo).bar(any(A.class), any(B.class), Mockito.<ConcurrentSkipListMap<Long, String>>any(), anyInt(), anyInt());
Mockito.doAnswer(inv -> {
    throw new RuntimeException("failed");
}).when(foo).bar(Mockito.any(A.class), 
                 Mockito.any(B.class), 
                 Mockito.any(ConcurrentSkipListMap.class), 
                 Mockito.anyInt(), 
                 Mockito.anyInt());

But nothing worked so far. Any help is appreciated.

1

There are 1 best solutions below

0
Dhawal Kapil On BEST ANSWER

While I can't figure out the mistake in your code since several classes are involved, I can provide you with a solution with imports that works. It may help you.

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;

import java.util.concurrent.ConcurrentSkipListMap;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import so.A;
import so.B;
import so.Foo;
import so.MyService;
import so.SomeOtherService;

@ExtendWith(SpringExtension.class)
@TestPropertySource(properties = { "spring.config.location=classpath:application-test.yml" })
public class MyServiceTest {

    @MockBean
    private SomeOtherService someOtherService;

    @SpyBean
    @Autowired
    private Foo foo;

    @Test
    public void test() {
    MyService myService = new MyService(foo);

    Mockito.doThrow(new RuntimeException("failed")).when(foo).bar(any(A.class), any(B.class),
        any(ConcurrentSkipListMap.class), anyInt(), anyInt());

    myService.execute();
    // some assertions
    }
}

Here are the dummy classes that I created that was not part of your question:

public class A {}
public class B {}
public class SomeOtherService {

    public void doSomething(A a, B b, ConcurrentSkipListMap<Long, String> c, int d, int e) {
    }
}