JUnit에서 예외 발생 검증 — @Test(expected) vs assertThrows()
1. 문제 정의
단위 테스트에서 “이 메서드는 특정 상황에서 특정 예외를 던져야 한다"를 검증할 때가 많습니다. 이 질문은 StackOverflow에서 조회 210만 회 이상 누적된 고전적인 주제입니다.
가장 흔하지만 장황한 구현이 try/catch + assertTrue 방식입니다.
| |
2. 원인 탐구
검증 의도는 같지만 표현하는 API가 JUnit 버전에 따라 달라졌습니다. 아래 표는 버전별 예외 검증 방법입니다.
| JUnit 버전 | 예외 검증 방법 | 특징 |
|---|---|---|
| JUnit 4.12 이하 | @Test(expected = X.class) | 예외 타입만 검증, 메시지 확인 불가 |
| JUnit 4.13+ | org.junit.Assert.assertThrows | 예외 객체 반환, 메시지 검증 가능 |
| JUnit 5 | org.junit.jupiter.api.Assertions.assertThrows | 람다 기반, assertThrowsExactly 지원 |
| 외부 라이브러리 | AssertJ / Google Truth | 메시지·원인 체이닝 검증 가능 |
3. 근본 원인 분석
try/catch + assertTrue 방식이 실무에서 문제가 되는 이유와, JUnit 버전 혼용 오해를 정리합니다.
증상 fingerprint
| 항목 | 내용 |
|---|---|
| 증상 | 테스트가 실패하지 않는데 검증이 빈번히 어긋나거나, assertThrows 미존재로 컴파일 에러 발생 |
| 발생 단계 | 단위 테스트 실행 시 Maven mvn test / Gradle ./gradlew test |
| 관련 도구 | Java, JUnit 4, JUnit 5, AssertJ, Google Truth |
| 흔한 오해 | JUnit 4.12 프로젝트에 assertThrows를 무심코 사용하거나, JUnit 5에서 @Test(expected=…)를 사용 |
| 빠른 판단 기준 | 먼저 사용 중인 JUnit 버전을 확인한 뒤 그 버전에 맞는 API 선택 |
원인 후보 매트릭스
| 원인 후보 | 확인 방법 | 맞을 때 증상 | 해결 방향 |
|---|---|---|---|
| JUnit 4.12 이하 | junit 의존성 버전 확인 | assertThrows 없다는 컴파일 에러 | @Test(expected=…) 또는 4.13+ 업그레이드 |
| JUnit 4.13+ | pom/build.gradle 확인 | assertThrows 정상 동작 | org.junit.Assert.assertThrows |
| JUnit 5 | junit-jupiter 확인 | Assertions.assertThrows 존재 | org.junit.jupiter.api.Assertions |
| try/catch 방식 | 코드 리뷰 | 예외가 안 나면 실패 원인 모호 | assertThrows로 교체 |
잘못된 해결책 — try/catch + assertTrue
| |
문제점:
- 예외가 발생하지 않으면 assertTrue(thrown)이 실패하지만, 다른 타입의 예외가 나면 그대로 전파되어 검증 의도가 모호해집니다.
- thrown 같은 플래그 변수로 코드가 장황해집니다.
- 예외 메시지 검증이 어렵습니다.
- assertThrows가 이 문제들을 API로 해결하므로 assertThrows를 우선합니다.
4. 코드 해결책
버전에 맞는 API를 사용해 예외 타입과 메시지를 검증합니다.
JUnit 4.12 이하 — @Test(expected = …)
| |
예외 타입만 확인하며 메시지는 검증할 수 없습니다.
JUnit 4.13+ / JUnit 5 — assertThrows
| |
- JUnit 4.13+는
import static org.junit.Assert.assertThrows;로 동일한 형태를 씁니다. - JUnit 5 하위 타입이 아닌 정확한 타입만 통과시키려면
assertThrowsExactly(ExpectedType.class, () -> ...)를 사용합니다.
검증 명령
| |
- 예외 유발 테스트가 기대대로 통과하는지 확인합니다.
5. 향후 예방 조치
프로젝트의 JUnit 버전을 먼저 확인하고 그 버전에 맞는 API를 사용합니다.
| 확인 항목 | 권장 예외 검증 API |
|---|---|
| JUnit 4.12 이하 | @Test(expected=…) |
| JUnit 4.13+ | org.junit.Assert.assertThrows |
| JUnit 5 | Assertions.assertThrows / assertThrowsExactly |
| AssertJ / Google Truth | assertThatThrownBy 등 체이닝 |
- 신규 테스트는 assertThrows 기반으로 작성합니다.
- 기존 try/catch + assertTrue 방식은 점진적으로 assertThrows로 교체합니다.
- 로컬과 CI에서 동일하게 검증 명령을 실행하여 버전 혼용으로 인한 재발을 막습니다.
DevTrace verdict
핵심 판단: 예외 검증이라는 의도는 같지만 JUnit 버전별로 API가 다르므로, 사용 중인 버전에 맞는 API를 선택하는 것이 핵심입니다.