2017-06-19 51 views
5

Przekształcałem kod w asynchroniczny. Oryginalny test jednostki użył adnotacji @Test(expected = MyExcpetion.class), ale nie sądzę, że to zadziała, ponieważ wyjątek, który chcę potwierdzić, jest zawarty w java.util.concurrent.ExcutionException. Próbowałem nazywając moją przyszłość tak, ale moje twierdzenie jest nadal nie i nie kocham, że muszę dodać w return nullJak mogę przetestować wyjątek w pełni możliwej do zrealizowania w przyszłości?

myApiCall.get(123).exceptionally((ex) -> { 
assertEquals(ex.getCause(),MyCustomException.class) 
return null 
} 

Próbowałem też tego smaku, ale nadal nie działa

myApiCall.get(123).exceptionally((ex) -> { 
assertThat(ex.getCause()) 
    .isInstanceOF(MyException.class) 
    .hasMessage("expected message etc") 
return null; 
} 

Mój interfejs API zgłasza wyjątek, jeśli nie może znaleźć identyfikatora. Jak powinienem to właściwie testować? Czy mogę mimo to użyć tej oryginalnej adnotacji?

Moje połączenie api dociera do db po uruchomieniu. W tym teście ustawiam swoją przyszłość, aby zwrócić błąd, więc właściwie nie próbuję się z niczym komunikować. kod badanego wygląda następująco

public class myApiCall { 
    public completableFuture get(final String id){ 
    return myService.getFromDB(id) 
    .thenApply( 
     //code here looks at result and if happy path then returns it after 
     //doing some transformation 
     //otherwise it throws exception 
    ) 
    } 
} 

w badanej jednostki wymusić myService.getFromDB(id) powrotu złych danych, dzięki czemu można przetestować wyjątek, a także zachować ten test jednostki nie docierać do db itp

+0

Przypuszczalnie twoi "dostają" delegatów do innego wątku. Zauważ, że JUnit nie czeka na zakończenie. Jeśli zakończy swoją metodę testową, zakończy proces Java. Zamiast tego 'get' na CF, aby czekać na wynik. Jeśli jest wyjątek, rozwiń go i zapewnij tam (lub wyrzuć go ponownie i użyj swojego 'expected'). –

+0

Co to jest myApiCall? – fxrbfg

+0

api po prostu dociera do db dla danych.pozwól mi zaktualizować coś w powyższym kodzie, więc być może jego jaśniejsze – Barry

Odpowiedz

4

Załóżmy, że Twój API rzuca jeśli wywołana 0:

public static CompletableFuture<Integer> apiCall(int id) { 
    return CompletableFuture.supplyAsync(() -> { 
    if (id == 0) throw new RuntimeException("Please not 0!!"); 
    else return id; 
    }); 
} 

można sprawdzić, że działa zgodnie z oczekiwaniami z następującego kodu (używam TestNG ale podejrzewam, że nie będzie zbyt trudne do przetłumaczenia na JUnit test):

@Test public void test_ok() throws Exception { 
    CompletableFuture<Integer> result = apiCall(1); 
    assertEquals(result.get(), (Integer) 1); 
} 

@Test(expectedExceptions = ExecutionException.class, 
     expectedExceptionsMessageRegExp = ".*RuntimeException.*Please not 0!!") 
public void test_ex() throws Throwable { 
    CompletableFuture<Integer> result = apiCall(0); 
    result.get(); 
} 

Należy zauważyć, że drugi test wykorzystuje fakt, że komunikat ExecutionException będzie zawierał oryginalny typ wyjątku i komunikat oraz przechwyci oczekiwanie za pomocą wyrażenia regularnego. Jeśli nie możesz tego zrobić z JUnit, możesz zadzwonić pod numer result.get() w bloku try/catch i zadzwonić pod numer throw e.getCause(); w bloku catch. Innymi słowy, coś jak to:

@Test(expectedExceptions = RuntimeException.class, 
     expectedExceptionsMessageRegExp = "Please not 0!!") 
public void test_ex() throws Throwable { 
    CompletableFuture<Integer> result = apiCall(0); 
    try { 
    result.get(); 
    } catch (ExecutionException e) { 
    throw e.getCause(); 
    } 
} 
+0

miałem problemy z smakiem JUnit, więc poszedłem ze stylem twojego testu powyżej zawijania '.get' w próbie catch i potwierdzeniu przyczyny wyjątku i to zadziałało. Kiedy próbowałem '@Test (expected = MyException.class)' miałem problemy, ale zastanawiam się, czy mogę powtórzyć wyjątek, który może działać, ale chciałem zrobić głębsze twierdzenia o błędzie, więc było OK przy próbie/catch. – Barry

0

Można spróbować także alternatywną opcję:

import org.hamcrest.core.IsInstanceOf; 
import org.junit.rules.ExpectedException; 

public class Test() { 

    @Rule 
    public ExpectedException thrown = ExpectedException.none(); 

    @Test 
    public void myApiCallTest() { 
     thrown.expect(ExcutionException.class); 
     thrown.expectCause(IsInstanceOf.instanceOf(MyException.class)); 
     thrown.expectMessage("the message you expected"); 
     myApiCall.get(""); 
    } 
} 

Zakładając, że:

public class myApiCall { 
    public completableFuture get(final String id) { 
     // ... 
     throw new ExcutionException(new MyException("the message you expected")) 
    } 
} 
1

że jest rzeczą łatwą robi w JUnit-4. Czy pamiętasz adnotację @RunWith? Tak, napisz własną TestRunner przechwycić wyjątek zanim procesor wyjątek JUnit oczekuje się powoływać na przykład:

public class ConcurrentRunner extends BlockJUnit4ClassRunner { 

    public ConcurrentRunner(Class<?> klass) throws InitializationError { 
     super(klass); 
    } 


    @Override 
    protected Statement possiblyExpectingExceptions(FrameworkMethod method, 
                Object test, 
                Statement next) { 
     return super.possiblyExpectingExceptions(
       method, test, throwingActualException(next) 
     ); 
    } 

    private Statement throwingActualException(Statement next) { 
     return new Statement() { 
      @Override 
      public void evaluate() throws Throwable { 
       try { 
        next.evaluate(); 
       } catch (ExecutionException | CompletionException source) { 
        throw theActualExceptionOf(source); 
       } 
      } 

      private Throwable theActualExceptionOf(Exception source) { 
       return source.getCause() != null ? source.getCause() : source; 
      } 
     }; 
    } 
} 

tylko opatrzone @RunWith(ConcurrentRunner.class) na badania, nie trzeba w ogóle zmieniać swój kod testowy. na przykład:

@RunWith(ConcurrentRunner.class) 
public class ConcurrentExpectedExceptionTest { 

    @Test(expected = IllegalArgumentException.class) 
    public void caughtTheActualException() throws Throwable { 
     myApiCall().join(); 
    } 

    private CompletableFuture<Object> myApiCall() { 
     return CompletableFuture.supplyAsync(() -> { 
      throw new IllegalArgumentException(); 
     }); 
    } 
}