단위 테스트
- 프로그램의 가장 작은 실행 단위가 의도대로 동작하는지 독립적으로 검증하는 테스트
외부 요인과 관계 없이, 코드 자체가 정확히 동작하는지 테스트
특징
1. 독립성 (Isolation)
- 단위 테스트는 실제 DB, 외부 API, 파일 시스템 등에 의존하면 안됨
- 테스트는 실패 원인이 코드 때문인지, DB 때문인지 구분이 안되기 때문
2. 빠른 속도
- 단위 테스트는 빨리 끝나야함 (ms)
- DB 쿼리, 네트워크 호출이 들어가면 속도가 급격히 느려짐
3. 반복 가능성
- 같은 테스트는 언제 돌려도 같은 결과가 나와야 함
- 외부 환경 영향을 받으면 단위 테스트가 아님
4. 예측 가능성
- 테스트 결과가 랜덤이면 안됨
- 의존 객체를 Mock으로 제어하기 때문에 결과를 사용자가 통제 함
Mock 객체
- 실제 객체처럼 동작하지만, 테스트를 위해 가짜로 만든 객체
- 단위 테스트가 외부 의존성에 영향을 받지 않도록 하려면 반드시 Mock객체 사용
Mock 필요성
| 이유 | 설명 |
|---|
| 속도 향상 | DB / 네트워크 없이 빠르게 테스트 |
| 독립성 확보 | 특정 클래스만 검증 |
| 예측 가능성 | 외부 객체의 응답을 강제로 지정 가능 |
| 안정성 | 네트워크 장애, DB 오류로 인한 테스트 실패 방지 |
Mockito 주요 어노테이션
| 어노테이션 | 설명 |
|---|
@Mock | 가짜 객체 생성 |
@InjectMocks | Mock 객체를 주입받아 테스트 대상 객체 생성 |
@ExtendWith(MockitoExtension.class) | Mockito 테스트 환경 사용 |
when(...).thenReturn(...) | Mock의 동작 정의 |
verify(..., times(n)) | 메서드 호출 횟수 검증 |
any() | 파라미터를 특정 값으로 제한하지 않고 수용 |
assertThrows() | 예외 발생 검증 |
Controller 단위 테스트
- Spring MVC 동작
- URL 매핑
- JSON 직렬화
- HTTP 요청/응답
- Service 호출
사용 도구
예시
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@DisplayName("비밀번호 변경 - AuthUser 없을 때 - 실패 케이스")
void ChangePassword_Not_AuthUser_fail() throws Exception {
mockMvc.perform(put("/users")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.detail").value("유효하지 않은 UserRole"));
}
}
핵심
- Controller 테스트는 Spring MVC 동작 + JSON 요청/응답 구조 + validation 만 테스트 할 것
Util 클래스 단위 테스트 - Mock 필요 없음
- 외부 의존성이 없기 때문에 Mock이 필요 없음
- 순수한 로직만 테스트
- 속도 매우 빠름
예시
class JwtUtilTest {
private JwtUtil jwtUtil;
@BeforeEach
void setUp() {
jwtUtil = new JwtUtil();
String secretKey = "QWEASDzxcASDzxcrtyfghvbnuiojklmqweasdzxcasdHFGHGFHGF";
ReflectionTestUtils.setField(jwtUtil, "secretKey", secretKey);
jwtUtil.init();
}
@Test
@DisplayName("JWT 토큰 생성 TEST - 성공 케이스")
void validateToken_success() {
Long userId = 1L;
String email = "lee@seo.jun";
UserRole role = UserRole.ADMIN;
String token = jwtUtil.createToken(userId, email, role);
assertThat(token).startsWith("Bearer ");
}
}
Service 단위 테스트
예시
@ExtendWith(MockitoExtension.class)
class ManagerServiceTest {
@Mock
private TodoRepository todoRepository;
@InjectMocks
private ManagerService managerService;
@Test
public void manager_목록_조회_시_Todo가_없다면_ManagerException_에러를_던진다() {
long todoId = 1L;
given(todoRepository.findById(todoId)).willReturn(Optional.empty());
ManagerException exception = assertThrows(ManagerException.class, () -> managerService.getManagers(todoId));
assertEquals("Todo not found", exception.getMessage());
}
}
핵심
Repository 외부 인프라에 의존해서 Mock객체 필수
given(), when()을 적절히 정의
- 반환값 검증