J'ai un service Spring:
@Service
@Transactional
public class SomeService {
@Async
public void asyncMethod(Foo foo) {
// processing takes significant time
}
}
Et j'ai un test d'intégration pour ce 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.
}
Voici le problème:
SomeService.asyncMethod(..)
est annoté avec @Async
etSpringJUnit4ClassRunner
adhère à la sémantique du @Async
le thread testAsyncMethod
fourchera l'appel someService.asyncMethod(testData)
dans son propre thread de travail, puis poursuivra directement l'exécution de verifyResults()
, peut-être avant que le thread de travail précédent ait terminé son travail.
Comment puis-je attendre la fin de someService.asyncMethod(testData)
avant de vérifier les résultats? Notez que les solutions à Comment puis-je écrire un test unitaire pour vérifier le comportement asynchrone à l'aide de Spring 4 et des annotations? ne le font pas appliquer ici, comme someService.asyncMethod(testData)
renvoie void
, pas un Future<?>
.
Pour @Async
sémantique à respecter, certains actifs @Configuration
la classe aura le @EnableAsync
annotation , par exemple.
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
//
}
Pour résoudre mon problème, j'ai introduit un nouveau profil Spring non-async
.
Si la non-async
le profil n'est pas actif, le AsyncConfiguration
est utilisé:
@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
}
Si le profil non asynchrone est actif, le NonAsyncConfiguration
est utilisé:
@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
}
Maintenant, dans la classe de test JUnit problématique, j'active explicitement le profil "non asynchrone" afin d'exclure mutuellement le comportement asynchrone:
@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.
}
Si vous utilisez Mockito (directement ou via le support de test Spring @MockBean
), il dispose d'un mode de vérification avec un délai d'expiration exactement pour ce cas: https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html # 22
someAsyncCall();
verify(mock, timeout(100)).someMethod();
Vous pouvez également utiliser Awaitility (je l'ai trouvé sur Internet, je ne l'ai pas essayé). 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) );
Si votre méthode retourne CompletableFuture
utilisez la méthode join
- documentation CompletableFuture :: join .
Cette méthode attend la fin de la méthode async et renvoie le résultat. Toute exception rencontrée est renvoyée dans le thread principal.
J'ai fait en injectant ThreadPoolTaskExecutor
et alors
executor.getThreadPoolExecutor (). expectTermination (1, TimeUnit.SECONDS);
avant de vérifier les résultats, procédez comme suit:
@Autowired
private ThreadPoolTaskExecutor executor;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
verifyResults();
}