web-dev-qa-db-fra.com

JUnit-test d'une méthode de service Spring @Async void

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:

  • comme SomeService.asyncMethod(..) est annoté avec @Async et
  • car le SpringJUnit4ClassRunner 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<?>.

21
Abdull

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.
}
20
Abdull

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) );
8
jhyot

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.

2
Jan Mares

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();
    }
0
bastiat