JUnit 5와 Mockito 테스트


JUnit 5는 Java 테스트 프레임워크이고, Mockito는 모킹 라이브러리입니다. 함께 사용하면 외부 의존성을 격리하고 단위 테스트를 효과적으로 작성할 수 있습니다.



언제 사용하나요?



  • Service 클래스 단위 테스트

  • Repository 의존성 모킹

  • 외부 API 호출 모킹

  • 예외 상황 테스트



의존성 추가


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- 포함됨: JUnit 5, Mockito, AssertJ -->


기본 테스트 구조


import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

private Calculator calculator;

@BeforeEach
void setUp() {
calculator = new Calculator();
}

@Test
@DisplayName("두 수의 합을 계산한다")
void add_shouldReturnSum() {
// given
int a = 10, b = 20;

// when
int result = calculator.add(a, b);

// then
assertEquals(30, result);
}

@Test
void divide_shouldThrowException_whenDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void isPositive_shouldReturnTrue(int number) {
assertTrue(calculator.isPositive(number));
}
}


Mockito 기본 사용


import org.mockito.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

@Mock
private UserRepository userRepository;

@InjectMocks
private UserService userService;

@Test
void findById_shouldReturnUser() {
// given
User mockUser = new User(1L, "홍길동", "hong@test.com");
when(userRepository.findById(1L))
.thenReturn(Optional.of(mockUser));

// when
User result = userService.findById(1L);

// then
assertNotNull(result);
assertEquals("홍길동", result.getName());
verify(userRepository, times(1)).findById(1L);
}

@Test
void findById_shouldThrowException_whenNotFound() {
// given
when(userRepository.findById(anyLong()))
.thenReturn(Optional.empty());

// then
assertThrows(UserNotFoundException.class, () -> {
userService.findById(999L);
});
}
}


Stubbing 패턴


// 단순 반환
when(mock.method()).thenReturn(value);

// 예외 발생
when(mock.method()).thenThrow(new RuntimeException());

// 연속 호출 다른 결과
when(mock.method())
.thenReturn(first)
.thenReturn(second)
.thenThrow(new Exception());

// 파라미터 매칭
when(mock.method(eq("specific")))
.thenReturn(value);
when(mock.method(anyString()))
.thenReturn(defaultValue);

// 콜백으로 동적 반환
when(mock.method(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
return new User(id, "User" + id);
});


검증 (Verification)


// 호출 횟수 검증
verify(mock, times(1)).method();
verify(mock, never()).method();
verify(mock, atLeast(2)).method();
verify(mock, atMost(3)).method();

// 호출 순서 검증
InOrder inOrder = inOrder(mock1, mock2);
inOrder.verify(mock1).firstMethod();
inOrder.verify(mock2).secondMethod();

// 파라미터 캡처
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
User captured = captor.getValue();
assertEquals("홍길동", captured.getName());


Spring Boot 통합 테스트


@SpringBootTest
@Transactional
class UserServiceIntegrationTest {

@Autowired
private UserService userService;

@Autowired
private UserRepository userRepository;

@Test
void createUser_shouldSaveToDatabase() {
// given
UserDto dto = new UserDto("홍길동", "hong@test.com");

// when
User saved = userService.createUser(dto);

// then
User found = userRepository.findById(saved.getId()).orElseThrow();
assertEquals("홍길동", found.getName());
}
}

// MockMvc로 Controller 테스트
@WebMvcTest(UserController.class)
class UserControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private UserService userService;

@Test
void getUser_shouldReturnUser() throws Exception {
when(userService.findById(1L))
.thenReturn(new User(1L, "홍길동"));

mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("홍길동"));
}
}


@Spy 사용


// 실제 객체 + 일부 메서드만 모킹
@Spy
private UserService userService = new UserService();

@Test
void test() {
// 특정 메서드만 stubbing
doReturn(mockUser).when(userService).findById(1L);

// 나머지는 실제 로직 실행
userService.processUser(1L);
}


테스트 생명주기


@BeforeAll      // 클래스 시작 전 1회
@BeforeEach // 각 테스트 전
@AfterEach // 각 테스트 후
@AfterAll // 클래스 종료 후 1회

@Disabled // 테스트 비활성화
@Timeout(5) // 시간 제한