2017-02-24 28 views
6

Mam usług wiosny:JUnit-testuje metodę usługi void Wiosna @Async

@Service 
@Transactional 
public class SomeService { 

    @Async 
    public void asyncMethod(Foo foo) { 
     // processing takes significant time 
    } 
} 

I mam test integracji tego 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. 
} 

Oto problem:

  • jako SomeService.asyncMethod(..) jest opatrzone przypisami @Async i
  • jako SpringJUnit4ClassRunner przestrzega @Async semantyki

wątek testAsyncMethod będzie widelec połączenia someService.asyncMethod(testData) do własnego wątku roboczego, a następnie bezpośrednio kontynuować wykonywanie verifyResults(), ewentualnie przed poprzedni pracownik wątek zakończył swoją pracę.

Jak mogę czekać na ukończenie someService.asyncMethod(testData) przed sprawdzeniem wyników? Zauważ, że rozwiązania do How do I write a unit test to verify async behavior using Spring 4 and annotations? nie mają tu zastosowania, ponieważ someService.asyncMethod(testData) zwraca void, a nie Future<?>.

Odpowiedz

8

Dla @Async semantyki, której należy przestrzegać, some active @Configuration class will have the @EnableAsync annotation, np.

@Configuration 
@EnableAsync 
@EnableScheduling 
public class AsyncConfiguration implements AsyncConfigurer { 

    // 

} 

Aby rozwiązać problem, wprowadziłem nowy profil wiosenny non-async.

Jeśli profil non-async jest nie aktywny, AsyncConfiguration jest stosowany:

@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 

} 

Jeśli profil nie asynchroniczny jest aktywny, NonAsyncConfiguration jest stosowany:

@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 

} 

teraz w problematycznej klasie testowej JUnit jawnie aktywuję profil "niezsynchronizowany" w celu wzajemnego wykluczenia zachowania asynchronicznego:

@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. 
} 
1

Jeśli używasz Mockito (bezpośrednio lub za pośrednictwem wiosennym testowania wsparcia @MockBean), posiada tryb weryfikacji z timeout dokładnie w tym przypadku: https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html#22

someAsyncCall(); 
verify(mock, timeout(100)).someMethod(); 

Można również użyć Awaitility (znalazłem go w Internecie, nie próbowałem go). 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)); 
0

W przypadku metoda zwraca CompletableFuture Użyj join metody - documentation CompletableFuture::join.

Ta metoda czeka na zakończenie metody asynchronicznej i zwraca wynik. Każdy napotkany wyjątek jest ponownie zgłaszany w wątku głównym.