I have a Spring service:
#Service
#Transactional
public class SomeService {
#Async
public void asyncMethod(Foo foo) {
// processing takes significant time
}
}
And I have an integration test for this SomeService:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest
#Transactional
public class SomeServiceIntTest {
#Inject
private SomeService someService;
#Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
verifyResults();
}
// verifyResult() with assertions, etc.
}
Here is the problem:
as SomeService.asyncMethod(..) is annotated with #Async and
as the SpringJUnit4ClassRunner adheres to the #Async semantics
the testAsyncMethod thread will fork the call someService.asyncMethod(testData) into its own worker thread, then directly continue executing verifyResults(), possibly before the previous worker thread has finished its work.
How can I wait for someService.asyncMethod(testData)'s completion before verifying the results? Notice that the solutions to How do I write a unit test to verify async behavior using Spring 4 and annotations? don't apply here, as someService.asyncMethod(testData) returns void, not a Future<?>.
For #Async semantics to be adhered, some active #Configuration class will have the #EnableAsync annotation, e.g.
#Configuration
#EnableAsync
#EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
//
}
To resolve my issue, I introduced a new Spring profile non-async.
If the non-async profile is not active, the AsyncConfiguration is used:
#Configuration
#EnableAsync
#EnableScheduling
#Profile("!non-async")
public class AsyncConfiguration implements AsyncConfigurer {
// this configuration will be active as long as profile "non-async" is not (!) active
}
If the non-async profile is active, the NonAsyncConfiguration is used:
#Configuration
// notice the missing #EnableAsync annotation
#EnableScheduling
#Profile("non-async")
public class NonAsyncConfiguration {
// this configuration will be active as long as profile "non-async" is active
}
Now in the problematic JUnit test class, I explicitly activate the "non-async" profile in order to mutually exclude the async behavior:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest
#Transactional
#ActiveProfiles(profiles = "non-async")
public class SomeServiceIntTest {
#Inject
private SomeService someService;
#Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
verifyResults();
}
// verifyResult() with assertions, etc.
}
I have done by injecting
ThreadPoolTaskExecutor
and then
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
before verifying results,
it as below:
#Autowired
private ThreadPoolTaskExecutor executor;
#Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
verifyResults();
}
If you are using Mockito (directly or via Spring testing support #MockBean), it has a verification mode with a timeout exactly for this case:
https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html#22
someAsyncCall();
verify(mock, timeout(100)).someMethod();
Much more capable is the great library Awaitility, which has many options how to handle async assertions. Example:
someAsyncCall();
await().atMost(5, SECONDS)
.untilAsserted(() -> assertThat(userRepo.size()).isEqualTo(1));
In case your method returns CompletableFuture use join method - documentation CompletableFuture::join.
This method waits for the async method to finish and returns the result. Any encountered exception is rethrown in the main thread.
Just to extend the answer by #bastiat, which in my opinion should be considered the correct one, you should also specified the TaskExecutor, if you are working with multiple executors. So you would need to inject the correct one that you wish to wait for. So, let's imagine we have the following configuration class.
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean("myTaskExecutor")
public TaskExecutor myTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(15);
executor.setCoreCapacity(10);
executor.setQueueCapacity(Integer.MAX_VALUE);
executor.setThreadNamePrefix("MyTaskExecutor-");
executor.initialize();
return executor;
}
// Everything else
}
Then, you would have a service that would look like the following one.
#Service
public class SomeServiceImplementation {
#Async("myTaskExecutor")
public void asyncMethod() {
// Do something
}
// Everything else
}
Now, extending on #bastiat answer, the test would look like the following one.
#Autowired
private SomeService someService;
#Autowired
private ThreadPoolTaskExecutor myTaskExecutor;
#Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
this.someService.asyncMethod(testData);
this.myTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
this.verifyResults();
// Everything else
}
Also, I have a minor recommendation that has nothing to do with the question. I wouldn't add the #Transactional annotation to a service, only to the DAO/repository. Unless you need to add it to a specific service method that must be atomic.
Just addition to the above solutions:
#Autowired
private ThreadPoolTaskExecutor pool;
#Test
public void testAsyncMethod() {
// call async method
someService.asyncMethod(testData);
boolean awaitTermination = pool.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
assertThat(awaitTermination).isFalse();
// verify results
}
Related
I have read many questions and answers about using #Scheduled with #Async in Spring but no one resolves my problem and my asynchronous method still runs single-threaded. So here is my Configuration class:
#EnableScheduling
#EnableAsync
#Configuration
#RequiredArgsConstructor
public class SchedulerConfiguration {
private final ThreadPoolProperties threadPoolProperties;
#Bean
public TaskExecutor commonTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(threadPoolProperties.getCorePoolSize()); // 10
taskExecutor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize()); // 20
taskExecutor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); // 5
taskExecutor.setThreadNamePrefix("TEST");
taskExecutor.initialize();
return taskExecutor;
}
}
Then we have a bean with the #Scheduled method:
#Component
#RequiredArgsConstructor
public class ScheduledTask {
private final ConfirmReservationTask confirmReservationTask;
#Scheduled(cron = "${booking.scheduler.confirmReservationsCron}")
public void process() {
confirmReservationTask.confirmReservations();
}
}
And finally, another bean (to avoid self-injection and proxy problems with asynchronous processing) with #Async method:
#Log4j2
#Component
#RequiredArgsConstructor
public class ConfirmReservationTask {
private final ReservationService reservationService;
#Async("commonTaskExecutor")
public void confirmReservations() {
...
}
}
unfortunately, this solution works in only one thread, however, the method uses the correct ThreadExecutor. How to solve it?
I have a MongoRepository interface:
public interface MyRepository extends MongoRepository<MyDocument,String> {}
and a #Component annotated class which serves as a thread using #Scheduled :
#Component
#Scope("prototype")
#EnableAsync
public class MyThread {
#Autowired
private MyRepository myRepository;
#Scheduled(fixedRate = 5000)
#Async
public void saveCurrentTime()
{
myRepository.save(someTimeHere);
}
}
My question is:
Is this #Scheduled method thread-safe ? The main idea here is to create different MyThread beans (#Scope("prototype")) which will save the current time in a NoSQL. But what happens if 2 of these "threads" call at the same time myRepository.save() method ? Should I handle by myself the synchronization or is it already handled with this annotation ?
I created a simple spring boot application with scheduled (#Scheduled) task. In that scheduled task, I would like to call async function with #Async, but I can see it still runs on the scheduling thread without switch to another thread. I also tried to customise executor, but no luck. Here are some codes.
I also already enable async in main class
public class scheduledService {
#Scheduled(fixedRateString = "${config.scheduleInterval}")
public void pollDataWithFixSchedule() {
AsyncService service = new AsyncService();
service.asyncCall();
service.asyncCall();
service.asyncCall();
asyncCall();
}
}
public class AsyncService {
#Async()
public void asyncCall(){
System.out.printly("Current thread -- {}",Thread.currentThread().getName()))
Thread.sleep(10000);
}
}
#Bean(name = "MyThreadPoolExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyThreadPoolExecutor-");
executor.initialize();
return executor;
}
#SpringBootApplication
#EnableScheduling
#EnableAsync
public class ScheduledApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ScheduledApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
according to Baeldung:
#Async has two limitations:
it must be applied to public methods only
self-invocation – calling the async method from within the same class – won't work
The reasons are simple – the method needs to be public so that it can be proxied. And self-invocation doesn't work because it bypasses the proxy and calls the underlying method directly.
so you can put your async method in a service and use it from there
you need to autowire AsyncService, do not create new object like
AsyncService service = new AsyncService();
Also, annotate your scheduledService class with #Service or #Component
#Service
public class scheduledService {
#Autowired
private AsyncService service ;
#Scheduled(fixedRateString = "${config.scheduleInterval}")
public void pollDataWithFixSchedule() {
service.asyncCall();
service.asyncCall();
service.asyncCall();
}
}
不要在同一个类中调用异步方法
Do not call asynchronous methods in the same class.
将异步任务单独放到一个类 并且在这个类上加上#Component
Put asynchronous tasks into a single class and add # component to this class
Use #EnableAsync on the top of class where you are creating async bean, not on the ScheduledApplication.
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
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.
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
}
}
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.