JUnit5 + Mockito: Given-When-Then 패턴
테스트가 길어지는 이유의 절반은 “준비/행동/검증”이 섞이기 때문입니다.
Given-When-Then으로 단계와 의도를 분리하면 테스트가 짧아지고,
실패 메시지도 명확해집니다.
JUnit 5와 Mockito(BDD 스타일)를 조합한 실무 템플릿을 소개합니다.
기본 셋업
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway pg;
@Mock StockRepository stock;
@InjectMocks OrderService sut; // System Under Test
@Test @DisplayName("재고와 결제가 성공하면 주문이 완료된다")
void place_ok() {
// Given
OrderRequest req = OrderRequest.of("A-100", 2, "card");
given(stock.has("A-100", 2)).willReturn(true);
given(pg.pay(any(), anyInt(), eq("card"))).willReturn(Approval.OK);
// When
OrderResult res = sut.place(req);
// Then
assertAll(
() -> assertEquals(Status.COMPLETED, res.status()),
() -> then(stock).should().reduce("A-100", 2),
() -> then(pg).should().pay(any(), eq(2), eq("card"))
);
}
}
예외/검증 시나리오
@Test @DisplayName("재고가 부족하면 예외를 던진다")
void place_fail_stock() {
// Given
given(stock.has("B-200", 5)).willReturn(false);
// When & Then
BusinessException ex = assertThrows(BusinessException.class,
() -> sut.place(OrderRequest.of("B-200", 5, "card")));
assertTrue(ex.getMessage().contains("재고"));
then(pg).shouldHaveNoInteractions();
}
파라미터화 테스트
@ParameterizedTest(name = "유효하지 않은 수량: {0}")
@ValueSource(ints = {0, -1, -99})
void invalid_quantity(int qty) {
assertThrows(IllegalArgumentException.class,
() -> sut.place(OrderRequest.of("A-100", qty, "card")));
}
Given-When-Then 정리법
- Given: 테스트 데이터, 목 행동, 고정된 시계/환경
- When: 딱 한 줄(대상 메서드 호출)
- Then: 결과 검증 + 목 상호작용 검증(순서, 호출 횟수)
검증 팁
then(pg).should(times(1)).pay(any(), anyInt(), anyString());
then(pg).should(never()).refund(any());
then(pg).shouldHaveNoMoreInteractions();
테스트 데이터 빌더
동일한 DTO 조합을 반복해서 만들지 않도록 빌더를 만듭니다.
class OrderRequestBuilder {
String sku = "A-100"; int qty = 1; String method = "card";
OrderRequestBuilder qty(int q){ this.qty = q; return this; }
OrderRequest build(){ return OrderRequest.of(sku, qty, method); }
}
시간/랜덤 고정
랜덤/현재시간 의존은 테스트 불안정의 주범입니다. 시계/랜덤을 주입하고 목으로 고정하세요.
@Mock Clock clock;
given(clock.instant()).willReturn(Instant.parse("2025-09-28T00:00:00Z"));
given(clock.getZone()).willReturn(ZoneId.of("UTC"));
리팩터링 체크리스트
- 테스트 이름은 “행동을 한국어로” 기술(
@DisplayName) - 한 테스트는 한 행동(여러 assertAll은 허용)
- 목은 외부 협력자에만. 내부 구현 세부 호출 검증은 절제
- 중복 데이터는 빌더/팩토리로 정리
👉 1편: Maven→Gradle 마이그레이션 함정 7가지
👉 2편: JUnit5 + Mockito: Given-When-Then 패턴
'Java & Spring' 카테고리의 다른 글
| 연관관계 주인/지연로딩 N+1 체크리스트 (0) | 2025.10.02 |
|---|---|
| Lombok 안전 사용 규칙(@Builder/@Value/@With) (0) | 2025.10.01 |
| Maven→Gradle 마이그레이션 함정 7가지 (0) | 2025.10.01 |
| Pageable 응답 DTO 규격(정렬·페이지 표준화) (0) | 2025.09.30 |
| Spring Cache로 API 3배 빠르게(@Cacheable/무효화) (0) | 2025.09.30 |