问题
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@Asyncand - as the
SpringJUnit4ClassRunneradheres to the@Asyncsemantics
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<?>.
回答1:
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.
}
回答2:
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();
You could also use Awaitility (found it on the internet, haven't tried it). https://blog.jayway.com/2014/04/23/java-8-and-assertj-support-in-awaitility-1-6-0/
someAsyncCall();
await().until( () -> assertThat(userRepo.size()).isEqualTo(1) );
回答3:
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.
回答4:
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();
}
来源:https://stackoverflow.com/questions/42438862/junit-testing-a-spring-async-void-service-method