Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock Asynchronous (@Async) method in Spring Boot using Mockito?

What is the best way to mock asynchronous (@Async) method with mockito? Provided service below:

@Service
@Transactional(readOnly=true)
public class TaskService {
    @Async
    @Transactional(readOnly = false)
    public void createTask(TaskResource taskResource, UUID linkId) {
        // do some heavy task
    }
}

Mockito's verification as below:

@RunWith(SpringRunner.class)
@WebMvcTest(SomeController.class)
public class SomeControllerTest {
    @Autowired
    MockMvc mockMvc;
    @MockBean    
    private TaskService taskService;
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    // other details omitted...

    @Test
    public void shouldVerify() {
        // use mockmvc to fire to some controller which in turn call taskService.createTask
        // .... details omitted
        verify(taskService, times(1)) // taskService is mocked object
            .createTask(any(TaskResource.class), any(UUID.class));
    } 
}

The test method shouldVerify above will always throw:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Misplaced argument matcher detected here:

-> at SomeTest.java:77) // details omitted
-> at SomeTest.java:77) // details omitted 

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

The exception above won't happend if I remove @Async from the TaskService.createTask method.

Spring Boot version: 1.4.0.RELEASE

Mockito version: 1.10.19

like image 936
Yudhistira Arya Avatar asked Aug 18 '16 10:08

Yudhistira Arya


2 Answers

Found out that by changing the Async mode to AspectJ fixed the issue:

@EnableCaching
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(lazyInit = true) 
@EnableAsync(mode = AdviceMode.ASPECTJ) // Changes here!!!
public class Main {
    public static void main(String[] args) {
        new SpringApplicationBuilder().sources(Main.class)
                                    .run(args);
    }
}

I'll accept this as a temporary hackish solution until I understand what's the real root cause of this issue.

like image 91
Yudhistira Arya Avatar answered Sep 21 '22 07:09

Yudhistira Arya


There's a bug in Spring Boot that we hope to fix in 1.4.1. The problem is that your mock TaskService is still being called asynchronously which breaks Mockito.

You could work around the problem by creating an interface for TaskService and creating a mock of that. As long as you leave the @Async annotation only on the implementation things will then work.

Something like this:

public interface TaskService {

    void createTask(TaskResource taskResource, UUID linkId);

}

@Service
@Transactional(readOnly=true)
public class AsyncTaskService implements TaskService {

    @Async
    @Transactional(readOnly = false)
    @Override
    public void createTask(TaskResource taskResource, UUID linkId) {
        // do some heavy task
    }

}
like image 25
Andy Wilkinson Avatar answered Sep 24 '22 07:09

Andy Wilkinson