Monday Short: Asynchrone Aufrufe testen

Monday Short: Asynchrone Aufrufe testen

David Seidel

10. August 2020

JAVA

TESTING

Intro

Asynchrone Aufrufe werden oft da gebraucht, wenn das “Hängen” eines Aufrufes den Mainthread nicht stören darf oder parallele Aufrufe ausgeführt werden sollen.

Beim Testen der Aufrufe sind zwei Aspekte interessant:
1. Geschieht der Aufruf wirklich asynchron?
2. Wird die Schnittstelle, die asynchron aufgerufen wird, mit den richtigen Werten versorgt?

Beispiel

Service

public interface Service {
   void doIt(String argument);
}

Asynchroner Aufrufer des Services

public class AsychronousServiceCaller implements Service {
   @Inject
   private Service service;
   
   @Inject
   private ExecutorService executorService;
 
   public void doIt(final String argument) {
      executorService.submit(() -> {
         service.doIt(argument);
      });
   }
}

Der asynchrone Aufrufer ist ein Delegate, das einen ExecutorService nutzt, um den Service aufzurufen. Dabei wird das Argument direkt durchgereicht.

Der Test

@ExtendWith(MockitoExtension.class)
class AsychronousServiceCallerTest {
    @Mock
    private Service mockService;

    @Mock
    private ExecutorService mockExecutorService;

    @InjectMocks
    private AsychronousServiceCaller asychronousServiceCaller;

    @Test
    public void doIt_serviceIsCalledAsynchronously() throws Exception {
        ArgumentCaptor<Runnable> captor = forClass(Runnable.class);
        asychronousServiceCaller.doIt("");
        verify(mockExecutorService).submit(captor.capture());
        verify(mockService, times(0)).doIt(any());

        captor.getValue().run();
        verify(mockService, times(1)).doIt(any());
    }

    @Test
    public void doIt_serviceIsCalledWithPassedArgument() throws Exception {
        String argument = "FOO";

        ArgumentCaptor<Runnable> captor = forClass(Runnable.class);
        asychronousServiceCaller.doIt(argument);
        verify(mockExecutorService).submit(captor.capture());

        captor.getValue().run();
        verify(mockService).doIt(eq(argument));
    }
}

Die Testklasse umfasst zwei Tests für die in der Einführung genannten Aspekte.

Der erste Test ruft den asynchronen Aufrufer auf und testet, ob der Service erst aufgerufen wird, wenn der ExecutorService dies tut.

Der zweite Test überprüft, ob das Argument richtig an den Service weitergegeben wird. Dazu wird das Lamba, dass an den Executor-Service übergeben wird, abgefangen und dann direkt ausgeführt.

Wie man sieht, ist das Testen von asychronen Aufrufen einfach und alle notwendigen Tools (JUnit, Mockito) sind leicht einzubinden.

(microphone-drop)