[Spring] 스프링 심화 트러블슈팅 기록
Spring 심화 과제를 진행하면서 겪은 트러블슈팅의 과정들에 대한 기록입니다.
해당 프로젝트의 전체소스는 여기 에서 확인하실 수 있습니다.
트러블슈팅
⭐️ 주제
@WebMvcTest에서 커스텀 HandleMethodArgumentResolver(AuthUserArgumentResolver)를 @MockBean으로 대체했는데도 진짜 Resolver가 실행되어 테스트가 실패하는 문제
🔥 발생
- 테스트 구성
@WebMvcTest(CommentController.class)@MockBean AuthUserArgumentResolver authUserArgumentResolver
- 기대: 가짜(Mock) Resolver가 동작
- 실제: 컨텍스트에 등록된 진짜 Resolver가 실행되며, 테스트 환경에선
JwtFilter가 동작하지 않기 때문에request에 값이 존재하지 않아 NPE 발생하여 테스트 실패
🔍 원인
DI 없이 new로 직접 등록
WebConfig가 new AuthUserArgumentResolver()로 객체를 직접 생성하면, 스프링 DI 컨테이너를 거치지 않는다.
따라서 @MockBean으로 만든 가짜 빈이 대체할 수 없다.
1
2
3
4
5
6
7
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandleMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserArgumentResolver()); // ⚠️ 직접 생성
}
}
@WebMvcTest의 동작 특성
@WebMvcTest는 웹 계층만 로딩하는 “미니 컨텍스트”를 띄운다.@MockBean은 컨테이너에 등록된 빈을 가짜로 교체한다.- 그러나 Resolver가 빈이 아닌 직접 생성된 객체라면,
@MockBean이 대체할 수 없다. - 따라서 ArgumentResolver 목록에는 진짜 객체가 등록된다.
✅ 해결
1️⃣ DI로 전환 - 자동으로 Mock 주입
WebConfig를 생성자 주입으로 변경하면, 테스트에서 @MockBean이 자동으로 그 자리를 차지한다.
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final AuthUserArgumentResolver authUserArgumentResolver; // 빈 주입
@Override
public void addArgumentResolvers(List<HandleMethodArgumentResolver> resolvers) {
resolvers.add(authUserArgumentResolver); // DI로 주입된 빈 사용
}
}
테스트 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebMvcTest(CommentController.class)
class CommentControllerTest {
private final AuthUser authUser = new AuthUser(1L, "test@test.com", UserRole.USER);
@Autowired
private MockMvc mockMvc;
@MockBean
private AuthUserArgumentResolver authUserArgumentResolver;
@BeforeEach
void setUp() throws Exception {
given(authUserArgumentResolver.supportsParameter(any()))
.willReturn(true);
given(authUserArgumentResolver.resolveArgument(any(), any(), any(), any()))
.willReturn(authUser);
}
@Test
// 테스트 메서드들 ...
}
@WebMvcTest가 만든 미니 컨텍스트에서WebConfig가 생성될 때@MockBean이 주입되어 가짜 Resolver가 등록된다.- 추가 수동 설정이 필요하지 않다.
2️⃣ 수동 MockMvc 구성 - 직접 가짜 Resolver 주입
@WebMvcTest를 사용하지 않고, MockMvcBuilders.standaloneSetup()으로 MockMvc를 직접 구성할 수도 있다.
1
2
3
4
5
6
7
8
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandleMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserArgumentResolver()); // 직접 생성
}
}
테스트 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CommentControllerTest {
private final AuthUser authUser = new AuthUser(1L, "test@test.com", UserRole.USER);
MockMvc mockMvc;
@Mock
CommentService commentService;
@Mock
AuthUserArgumentResolver authUserArgumentResolver;
@BeforeEach
void setUp() throws Exception {
given(authUserArgumentResolver.supportsParameter(any()))
.willReturn(true);
given(authUserArgumentResolver.resolveArgument(any(), any(), any(), any()))
.willReturn(authUser);
mockMvc = MockMvcBuilders.standaloneSetup(new CommentController(commentService))
.setCustomArgumentResolvers(authUserArgumentResolver) // 직접 가짜 Resolver 주입
.build();
}
@Test
// 테스트 메서드들 ...
}
WebConfig의new등록을 무시하고, 가짜 Mock Resolver를 직접 주입한다.- 💥 자동 설정되던
@WebMvcTest의 기능들을 Builder를 통해 수동으로 구성해야 한다.
💡 결론
- 프레임워크 확장 포인트(
ArgumentResolver,Interceptor등)는 반드시 DI 주입 방식으로 등록해야 한다. new로 직접 생성하면, 테스트에서 Mocking이 불가능해진다.- 이 원칙을 지키면
@WebMvcTest만으로 깔끔하고 유지보수성 좋은 테스트가 가능하다.