- H2 (Repository 테스트 or 통합 테스트용)
dependencies {
testRuntimeOnly 'com.h2database:h2'
}
Annotation 종류
1. @DataJpaTest
- JPA와 관련된 Component들만 모두 가지고와 Repository Layer 단위 테스트를 위한 Annotation이다.
- 기본적으로 In-memory DB(ex H2)를 사용하여 테스팅
요약: JPA 레포지토리 단위 테스트
2. @ExtendWith
- Junit5 환경에서 확장기능을 사용할때 사용.
- 주로 MockitoExtension 과 함께 Service Layer, 클래스 단위 테스트에 사용
요약: 서비스 단위 테스트
3. @WebMvcTest
- 스프링의 Web Layer(controller, filter 등)을 테스트하기 위한 Annotation이다.
- @Controller, @ControllerAdvice 등 웹과 관련된 Bean만 로드하여 테스트를 수행한다.
요약: 컨트롤러 단위 테스트
4. @SpringBootTest
- 스프링 부트 전체를 테스트 수행하기 위해서 사용하는 Annotation이다.
- 서버를 실행하듯 모든 스프링 Context를 로드한다.
요약: 통합 테스트
Mocking
테스트 코드를 작성하면 테스트를 하기 위한 코드 이외의 의존 객체들이 존재하는 경우가 있습니다. 예를 들면 Service 코드를 테스트 하는데 Repository가 필요한 경우이죠.
사용 이유
- 외부 의존성 제거
- 테스트 범위 준수
- 에러 상황 강제 발생
행위 검증(Behavior Validation), 상태 검증(State Validation)
- 행위 검증
- **“테스트시 특정한 행위를 하였는가”**를 확인하는 행위이다.
- 테스트를 하려는 코드가 어떤 메소드를 호출하였는지 하지 않았는지, 몇번 수행했는지 확인.
- Mockito의 verify() 메소드를 통해 검증
- 상태 검증
- **“기능 수행 후 결과값이 기대값과 일치하는가?”**를 확인하는 행위이다.
- 반환값, DB 저장된 데이터 등 값에 대해 의도한대로 반환/저장이 되는지 확인.
- application-test.properties
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;
spring.datasource.username=root
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database=h2
(default로 테스트 코드는 H2를 사용)
Repository Test

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void 이메일로_사용자를_조회할_수_있다() {
// given
String email = "asd@asd.com";
User user = new User(email, "password", UserRole.USER);
userRepository.save(user);
// when
User foundUser = userRepository.findByEmail(email).orElse(null);
// then
assertNotNull(foundUser);
assertEquals(email, foundUser.getEmail());
assertEquals(UserRole.USER, foundUser.getUserRole());
}
}
Service 테스트
- @Mock: 테스트 대상 외의 의존 객체
- @InjectMocks: 테스트 대상 객체
케이스1

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock // 테스트 대상 아님
private UserRepository userRepository;
@InjectMocks // 테스트 대상
private UserService userService;
@Test
void User를_ID로_조회할_수_있다() {
// given
String email = "asd@asd.com";
long userId = 1L;
User user = new User(email, "password", UserRole.USER);
ReflectionTestUtils.setField(user, "id", userId);
given(userRepository.findById(anyLong())).willReturn(Optional.of(user));
// when
UserResponse userResponse = userService.getUser(userId);
// then
assertThat(userResponse).isNotNull();
assertThat(userResponse.getId()).isEqualTo(userId);
assertThat(userResponse.getEmail()).isEqualTo(email);
}
@Test
void 존재하지_않는_User를_조회_시_InvalidRequestException을_던진다() {
// Given
long userId = 1L;
given(userRepository.findById(anyLong())).willReturn(Optional.empty());
// When & Then
assertThrows(InvalidRequestException.class,
() -> userService.getUser(userId),
"User not found"
);
}
케이스2

@Test
void User를_삭제할_수_있다() {
// given
long userId = 1L;
given(userRepository.existsById(anyLong())).willReturn(true);
doNothing().when(userRepository).deleteById(anyLong());
// when
userService.deleteUser(userId);
// then
verify(userRepository, times(1)).deleteById(userId);
}
@Test
void 존재하지_않는_User를_삭제_시_InvalidRequestException를_던진다() {
// given
long userId = 1L;
given(userRepository.existsById(userId)).willReturn(false);
// when & then
assertThrows(InvalidRequestException.class, () -> userService.deleteUser(userId));
verify(userRepository, never()).deleteById(userId);
}
}
Controller Test
@MockBean: Bean을 Mocking(3.4.x 이전. 3.4.x부터는 deprecated되어 사용 불가)
@MockitoBean: Bean을 Mocking(3.4.x 이후)
MockMvc: 스프링 MVC 컨트롤러를 테스트할 수 있게 해주는 모의 HTTP 요청/응답 도구
MockMvcTester: MockMvc의 래핑(상위 추상화) 버전, AssertJ와 더 잘 통합될 수 있음**(3.4.x 이후)**

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void User_목록_조회_빈리스트() throws Exception {
// given
given(userService.getUsers()).willReturn(List.of());
// when & then
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isEmpty());
}
@Test
void User_목록_조회() throws Exception {
// given
long userId1 = 1L;
long userId2 = 2L;
String email1 = "user1@a.com";
String email2 = "user2@a.com";
List<UserResponse> userList = List.of(
new UserResponse(userId1, email1),
new UserResponse(userId2, email2)
);
given(userService.getUsers()).willReturn(userList);
// when & then
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(2))
.andExpect(jsonPath("$[0].id").value(userId1))
.andExpect(jsonPath("$[0].email").value(email1))
.andExpect(jsonPath("$[1].id").value(userId2))
.andExpect(jsonPath("$[1].email").value(email2));
}
@Test
void User_단건_조회() throws Exception {
// given
long userId = 1L;
String email = "a@a.com";
given(userService.getUser(userId)).willReturn(new UserResponse(userId, email));
// when & then
mockMvc.perform(get("/users/{userId}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(userId))
.andExpect(jsonPath("$.email").value(email));
}
}
통합 테스트
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private JwtUtil jwtUtil;
@Test
@DisplayName("회원가입 후 User 단건 조회를 한다.")
void getUserIntegrationTest() {
// 회원가입
String signupUrl = "http://localhost:" + port + "/auth/signup";
SignupRequest signupRequest = new SignupRequest("user@example.com", "password", "USER");
ResponseEntity<SignupResponse> signupResponse = restTemplate.postForEntity(signupUrl, signupRequest, SignupResponse.class);
// 회원가입 검증
assertThat(signupResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
SignupResponse signupBody = signupResponse.getBody();
assertThat(signupBody).isNotNull();
String token = signupBody.getBearerToken();
assertThat(token).isNotBlank();
// JWT 에서 userId 추출
String rawToken = jwtUtil.substringToken(token);
Claims claims = jwtUtil.extractClaims(rawToken);
Long userIdFromToken = Long.valueOf(claims.getSubject());
assertThat(userIdFromToken).isNotNull();
// User 정보 단건 조회
String url = "http://localhost:" + port + "/users/" + userIdFromToken;
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
ResponseEntity<UserResponse> userResponse = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
UserResponse.class
);
// 검증
assertThat(userResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
UserResponse responseBody = userResponse.getBody();
assertThat(responseBody).isNotNull();
assertThat(responseBody.getId()).isEqualTo(userIdFromToken);
assertThat(responseBody.getEmail()).isEqualTo("user@example.com");
}
}'spring' 카테고리의 다른 글
| @WebMvcTest IllegalArgumentException: JPA metamodel must not be empty 오류 뜰때 (0) | 2025.03.06 |
|---|---|
| swagger 사용시 유용함. @Tag, @Operation (0) | 2025.03.06 |
| 페이징처리할때 수동으로 처리하면서 공부한 내용 (0) | 2025.03.05 |
| JPA 사용시 변경사항이 있을때 save() 가 아닌 @Transactional 사용해도 저장됨 (0) | 2025.02.26 |
| 관계형DB일때 N:M 연관관계 @ManyToMany (0) | 2025.02.24 |