주로 오류에 대한 모니터링 메시지 등은 logical flow에 크게 영향을 주지 않기 때문에 @Async
를 사용하여 분리하는 경우가 많다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Service public class FooSendService { public void send(Foo foo) { try { FooResponse response = fooSender.send(foo); ... } catch (Exception e) { kakaoTalkSender.send("Error"); } } } @Component public class KakaoTalkSender { @Async public Future<Boolean> send(String message){ return send(id, message); } } |
그런데 이런 경우 Future type의 return을 가지면 어떻게 테스트 코드를 만들어야 할 지 곤란한 경우가 있어서 기록해둔다. 1
이럴 때 사용하는 것이 Guava Futures.java
이다. (역시 믿고 쓰는 구글)
1 2 3 4 5 6 7 8 9 10 11 |
private void givenSuccessKakaoTalkSender() { when(kakaoTalkSender.send(anyString())).thenReturn(Futures.immediateFuture(true)); } private void givenFailureKakaoTalkSender() { when(kakaoTalkSender.send(anyString())).thenReturn(Futures.immediateFuture(false)); } private void givenExceptionKakaoTalkSender() { when(kakaoTalkSender.send(anyString())).thenReturn(Futures.immediateFailedFuture(new RuntimeException("오류"))); } |
ImmediateFuture
는 ListenableFuture > Future 에 대한 구현체이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
@Beta public final class Futures { public static <V> ListenableFuture<V> immediateFuture(@Nullable V value) { return new ImmediateSuccessfulFuture<V>(value); // Future에 대한 성공 객체 } public static <V> ListenableFuture<V> immediateFailedFuture(Throwable throwable) { checkNotNull(throwable); return new ImmediateFailedFuture<V>(throwable); // Future에 대한 오류 객체 } private static class ImmediateSuccessfulFuture<V> extends ImmediateFuture<V> { @Nullable private final V value; ImmediateSuccessfulFuture(@Nullable V value) { this.value = value; } @Override public V get() { // ImmediateFuture class를 상속받아 get()을 구현함 return value; } } private static class ImmediateFailedFuture<V> extends ImmediateFuture<V> { private final Throwable thrown; ImmediateFailedFuture(Throwable thrown) { this.thrown = thrown; } @Override public V get() throws ExecutionException { // ImmediateFuture class를 상속받아 get()을 구현함 throw new ExecutionException(thrown); } } private abstract static class ImmediateFuture<V> implements ListenableFuture<V> { private static final Logger log = Logger.getLogger(ImmediateFuture.class.getName()); @Override public void addListener(Runnable listener, Executor executor) { // ListenableFuture 구현 메소드 checkNotNull(listener, "Runnable was null."); checkNotNull(executor, "Executor was null."); try { executor.execute(listener); } catch (RuntimeException e) { // ListenableFuture's contract is that it will not throw unchecked // exceptions, so log the bad runnable and/or executor and swallow it. log.log(Level.SEVERE, "RuntimeException while executing runnable " + listener + " with executor " + executor, e); } } @Override public boolean cancel(boolean mayInterruptIfRunning) { // Future 구현 메소드 return false; } @Override public abstract V get() throws ExecutionException; // 하위 클래스에 구현을 위임함 @Override public V get(long timeout, TimeUnit unit) throws ExecutionException { // Future 구현 메소드 checkNotNull(unit); return get(); } @Override public boolean isCancelled() { // Future 구현 메소드 return false; } @Override public boolean isDone() { // Future 구현 메소드 return true; } } } // ImmediateFuture는 ListenableFuture 구현체인데, 이건 Future 타입을 상속함 public interface ListenableFuture<V> extends Future<V> { void addListener(Runnable listener, Executor executor); } |