@Test(timeout = 5000)
public void testLongRunningTaskWithCallback() throws Exception {

  // Instantiate a new executor and start a new 'null' task ...
  final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
  final Object result = new Object();
  final Callable<Object> task = mock(Callable.class);
  when(task.call()).thenAnswer(i -> {
    Thread.sleep(1500);
    return result;
  });
  final AsyncCallback<Object> callback = mock(AsyncCallback.class);
  final AsyncResult<Object> asyncResult = executor.startProcess(task, callback);
  assertNotNull(asyncResult);
  assertFalse(asyncResult.isCompleted());
  verifyZeroInteractions(callback);
  try {
    asyncResult.getValue();
    fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
  } catch (IllegalStateException e) {
    assertNotNull(e.getMessage());
  }
  // Our task should only execute once, but it can take a while ...
  verify(task, timeout(3000).times(1)).call();
  final ArgumentCaptor<Optional<Exception>> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class);
  verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture());
  final Optional<Exception> optionalException = optionalCaptor.getValue();
  assertNotNull(optionalException);
  assertFalse(optionalException.isPresent());
  // Prevent timing issues, and wait until the result is available
  asyncResult.await();
  assertTrue(asyncResult.isCompleted());
  verifyNoMoreInteractions(task, callback);
  // ... and the result should be exactly the same object
  assertSame(result, asyncResult.getValue());
}