본문 바로가기
Java & Spring

JUnit5 + Mockito: Given-When-Then 패턴

by yamoojin83 2025. 10. 1.

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 패턴

👉 3편: Lombok 안전 사용 규칙(@Builder/@Value/@With)

👉 4편: IntelliJ 생산성: 라이브 템플릿 10개