우테코 레벨 2 주문 기능 개발 미션을 수행하던 중, 주문 정보 관련 객체인 "Order"를 생성할 때 생성자 이내에 검증 로직을 추가했다.
따라서 해당 객체의 정상적 생성을 테스트하기 위해 다음과 같은 테스트를 진행했다. 객체를 생성할 때 정상적이면 아무런 예외가 던져지지 않음을 테스트하기 위한 것이다.
class OrderTest {
@Test
void 인출한_금액과_포인트를_합친_지불금액이_총_가격과_같으면_주문이_생성된다() {
final List<OrderDetail> orderDetails = List.of(new OrderDetail(new Product("참치", 1000, "이미지"), 1L),
new OrderDetail(new Product("김치", 2000, "이미지"), 2L));
final Payment payment = new Payment(BigDecimal.valueOf(4000));
final Point point = new Point(BigDecimal.valueOf(1000));
final Member member = new Member(1L, "이메일", "비밀번호");
assertThatThrownBy(
() -> new Order(orderDetails, payment, point, member)
).doesNotThrowAnyException();
}
}
그러나 테스트를 실행시키니 다음과같은 에러 코드를 마주하게 되었다.
java.lang.AssertionError:
Expecting code to raise a throwable.
분명 ".doesNotThrowAnyException()" 옵션을 사용했기에 테스트가 성공했어야 정상적이라고 생각했는데, 왜 실패했을까?
먼저 JavaDoc에 존재하는 AssertThatThrownBy의 설명을 죽 긁어와 보자
public static AbstractThrowableAssert<?,? extends Throwable> assertThatThrownBy(ThrowableAssert.ThrowingCallable shouldRaiseThrowable)
Allows to capture and then assert on a Throwable more easily when used with Java 8 lambdas.
Example :@Test public void testException() { assertThatThrownBy(() -> { throw new Exception("boom!"); }).isInstanceOf(Exception.class) .hasMessageContaining("boom"); }
If the provided ThrowableAssert. ThrowingCallable does not raise an exception, an error is immediately raised, in that case the test description provided with as(String, Object...) is not honored. To use a test description, use catchThrowable(ThrowableAssert.ThrowingCallable) as shown below.
// assertion will fail but "display me" won't appear in the error assertThatThrownBy(() -> { // do nothing }).as("display me").isInstanceOf(Exception.class); // assertion will fail AND "display me" will appear in the error Throwable thrown = catchThrowable(() -> { // do nothing }); assertThat(thrown).as("display me").isInstanceOf(Exception.class);
Parameters:
shouldRaiseThrowable - The ThrowableAssert.ThrowingCallable or lambda with the code that should raise the throwable.
Returns:
The captured exception or null if none was raised by the callable
주요 내용을 요약하자면
- 자바 8 람다식을 사용해 예외를 더 쉽게 검출(진단?)하기 위한 도구이다
- 맨 위의 코드(shouldRaiseThrowable)에서 유추할 수 있듯, 반드시 예외를 발생하는 코드가 존재해야 한다
- 테스트하는 코드(ThrowableAssert.ThrowingCallable) 에서 예외가 발생하지 않으면, 즉각 에러가 터진다.
- 따라서 ThrowableAssert.ThrowingCallable이나 람다식에서 예외가 발생하는 코드가 발생해야 한다.
즉, "AssertThatThrownBy"는 "에러가 발생해야 하는 상황"을 테스트하는 코드인 것이다.
"에러가 터져야 하는데 터지지 않네?" -> "실패" 라고 생각하자.
그렇다면 내 코드가 정상적으로 실행되는 것을 검증하려면 어떤 기능을 사용할 수 있을까?
답은 위의 제목에서 유추할 수 있듯 AssertThatCode를 사용하는 것이다.
역시 javadoc의 AssertThatCode 설명을 읽어보도록 하자
public static AbstractThrowableAssert<?,? extends Throwable> assertThatCode(ThrowableAssert.ThrowingCallable shouldRaiseOrNotThrowable)
Allows to capture and then assert on a Throwable (easier done with lambdas).
The main difference with assertThatThrownBy(ThrowingCallable) is that this method does not fail if no exception was thrown.
Example :ThrowingCallable boomCode = () -> { throw new Exception("boom!"); }; ThrowingCallable doNothing = () -> {};
// assertions succeed assertThatCode(doNothing).doesNotThrowAnyException(); assertThatCode(boomCode).isInstanceOf(Exception.class) .hasMessageContaining("boom");
// assertion fails assertThatCode(boomCode).doesNotThrowAnyException(); Contrary to assertThatThrownBy(ThrowingCallable) the test description provided with as(String, Object...) is always honored as shown below. ThrowingCallable doNothing = () -> {};
// assertion fails and "display me" appears in the assertion error assertThatCode(doNothing).as("display me") .isInstanceOf(Exception.class);
This method was not named assertThat because the java compiler reported it ambiguous when used directly with a lambda :(
Parameters:
shouldRaiseOrNotThrowable - The ThrowableAssert.ThrowingCallable or lambda with the code that should raise the throwable.
Returns:
the created ThrowableAssert.
맨 위의 설명에서 볼 수 있듯, AssertThatThrownBy와 달리 ThrowableAssert.ThrowingCallable가 shouldRaiseOrNotThrowable 변수로 선언되었다. 변수 명이 "예외를 던지면 안된다"이다. 느낌이 오지 않는가?
다음 설명에서도 나와 있다.
The main difference with assertThatThrownBy(ThrowingCallable) is that this method does not fail if no exception was thrown.
-> assertThatThrownBy와의 주요 차이는 예외가 발생하지 않을 때 테스트가 실패하지 않는다는 것이다!
즉, assertThatCode는 "실패하지 않는 코드"를 검증하는 것이다.
"에러가 터지면 안되는데 터지네?" -> "실패" 라고 생각하자
assertThatThrownBy -> 무조건 에러가 발생해야 한다. 에러가 발생하지 않으면 실패
assertThatCode -> 에러가 발생하면 안된다. 에러가 발생하면 실패