코딩/sparta TIL
TIL 52 : 테스트코드에 왜 AuthUser가 포함되지 않는가
americanoallday
2025. 5. 9. 20:50
✅ 먼저: @Auth AuthUser는 어떻게 들어오는가?
✔️ 실 서비스에서는:
@PostMapping("/todos")
public ResponseEntity<?> saveTodo(@Auth AuthUser authUser, ...)
이럴 때 Spring은
- 클라이언트가 HTTP 요청을 보냄 (ex. Authorization 헤더 포함)
- Spring은 @Auth라는 커스텀 어노테이션을 보고
- HandlerMethodArgumentResolver가 실행돼서
- 요청에서 유저 정보를 꺼내고 → AuthUser 객체를 만들어 자동 주입
❌ 그런데 테스트(MockMvc)에서는?
mockMvc.perform(post("/todos")
.contentType(MediaType.APPLICATION_JSON)
.content(...))
이건 그냥 JSON 본문만 보내는 “단순한 가짜 HTTP 요청”일 뿐
Spring Security 필터도 안 돌고,
Authorization 헤더도 없고,
ArgumentResolver도 등록 안 되어 있음
즉, 실서버에서 자동으로 주입되는 AuthUser는
MockMvc에서는 아무런 정보도 없이 null이 됨
🔥 비유
진짜 서비스: “문 앞에 출입증을 보여주면, 경비원이 너의 이름과 권한을 확인해서 컨트롤러에 알려줌”
MockMvc 테스트: “출입증 없이 문을 똑똑 두드리는데, 경비원이 없어” → AuthUser는 당연히 주입되지 않음 = null
그냥 직접 작성한 리졸버를 주입시켜서 해결함 ㅎㅎ 밑에 해결방법으로 안됨
@ExtendWith(MockitoExtension.class)
class TodoControllerTest {
private MockMvc mockMvc;
@Mock
private TodoService todoService;
@InjectMocks
private TodoController todoController;
private final ObjectMapper objectMapper = new ObjectMapper();
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(todoController)
.setCustomArgumentResolvers(new TestAuthUserArgumentResolver())
.build();
}
@Test
@DisplayName("todo 저장 성공 테스트")
void todo_저장_성공() throws Exception {
// given
AuthUser authUser = new AuthUser(1L, "test@example.com",UserRole.USER);
User user = User.fromAuthUser(authUser);
TodoSaveRequest request = new TodoSaveRequest("title","content");
TodoSaveResponse response = new TodoSaveResponse(1L,"title","content","Sunny",new UserResponse(user.getId(),user.getEmail()));
// when
when(todoService.saveTodo(any(AuthUser.class),any(TodoSaveRequest.class))).thenReturn(response);
// then
mockMvc.perform(post("/todos")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.title").value("title"));;
}
public class TestAuthUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Auth.class); // 너희 @Auth 조건
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
return new AuthUser(1L, "test@example.com", UserRole.USER); // Dummy 유저
}
}
}
✅ 그래서 해결법은?
해결법 | 설명 |
① @Auth 제거 | 테스트용으로 컨트롤러 파라미터 단순화 |
② ArgumentResolver 목킹 | AuthUser를 자동으로 주입하도록 테스트 코드에서 구현 |
③ 통합 테스트로 전환 | 실제 인증 로직 포함한 테스트로 변경 (@SpringBootTest + 인증 헤더 등) |
✅ 한 줄 요약
MockMvc는 진짜 필터 체인과 인증/리졸버를 통과하지 않기 때문에, @Auth AuthUser는 직접 주입하거나 흉내내야만 동작 함.
테스트코드에 리졸버 클레스 추가해서 해결함
@TestConfiguration // 🔸 테스트 전용 스프링 설정 클래스
static class ResolverConfig {
@Bean // 🔸 스프링이 자동으로 이 객체를 빈으로 등록
public HandlerMethodArgumentResolver testAuthArgumentResolver() {
return new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
return new AuthUser(1L, "test@email.com", UserRole.USER);
}
};
}
}
1. @TestConfiguration
- 테스트에서만 사용하는 설정 클래스
- 실제 애플리케이션이 실행될 땐 이 설정은 무시됨
- 내부에 정의된 @Bean 들은 테스트에서만 작동
2. @Bean HandlerMethodArgumentResolver
@Bean
public HandlerMethodArgumentResolver testAuthArgumentResolver() {
- HandlerMethodArgumentResolver는 Spring MVC가 컨트롤러 파라미터를 해석할 때 사용하는 인터페이스
- 여기서는 @Auth AuthUser를 처리할 리졸버를 직접 정의하는 것
3. supportsParameter(...)
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthUser.class);
}
- 이 리졸버가 어떤 파라미터를 지원할지 판단 즉, AuthUser 타입인 파라미터에만 동작하게 제한함
4. resolveArgument(...)
public Object resolveArgument(...) {
return new AuthUser(1L, "test@email.com", UserRole.USER);
}
- 실제로 AuthUser 파라미터가 필요할 때 여기를 타고 더미 유저 객체를 주입해줌 (테스트 전용 유저)
When에서 에러 발생
when(todoService.saveTodo(any(AuthUser.class), eq(request))).thenReturn(response);
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(any(), eq("String by matcher"));
✅ 핵심 메시지
“when using matchers, all arguments have to be provided by matchers”
🔥 의미:
- when(...).thenReturn(...) 에서 메서드의 파라미터를 지정할 때,
- 어떤 파라미터에 any() 같은 matcher를 썼다면 → 다른 파라미터도 matcher로 써야 한다!
❌ 잘못된 코드 (혼용)
when(todoService.saveTodo(any(), new TodoSaveRequest(...)))
.thenReturn(response);
- 첫 번째 인자는 matcher any()
- 두 번째 인자는 그냥 실제 값
- ❌ 혼용하면 Mockito가 예외 터뜨림
✅ 올바른 코드
when(todoService.saveTodo(any(), eq(new TodoSaveRequest("title", "content"))))
.thenReturn(response);
- eq(...) = “equal to 이 값이어야 함”이라는 matcher 이걸 써야 any()와 함께 사용할 수 있음
리졸버 클래스를 테스트 코드에 만들었는데 인식을 못함.
- @WebMvcTest는 필요한 컴포넌트만 최소한으로 로딩함
- 내부 static class인 ResolverConfig는 Spring이 자동으로 스캔하지 않음
- 그래서 @Import(...)로 명시적으로 등록해야 함
@WebMvcTest(TodoController.class)
@Import(TodoControllerTest.ResolverConfig.class) // 👈 이거 추가
class TodoControllerTest {
...
}