| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 28 | 29 | 30 | 31 |
- DDD
- 전전긍긍
- 공통규약
- 이벤트핸들러등록
- 단위테스트
- Transaction
- 냄새라도
- AOP
- string
- 디스크i/o
- SpringLegacy
- JDBC
- TDD
- SEQUENCE
- 설정세팅
- JPA
- MVC요청플로우
- 이벤트핸들러this
- 문제선택근거
- model
- 공통기술
- 클린아키텍처
- springsecurity
- index
- 로그백
- 커뮤니티서버
- OOP
- 사면초가
- MVC
- Ajax
- Today
- Total
개발이군고구마
[TDD] 단위 테스트에서 만난 오류와 원리들 본문
1. Mock 에서 NPE
ㄴ 스프링 컨텍스트가 만든 Controller 빈(=MockMvc가 부르는 인스턴스)에는 component가 주입되지 않음
ㄴ 즉, @InjectMocks로 생성한 객체와 MockMvc가 사용하는 객체가 다른 인스턴스
@ExtendWith(MockitoExtension.class)
class ControllerTest {
private MockMvc mockMvc;
@Mock
private Component component;
@InjectMocks
private Controller controller;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller) // 스프링 컨텍스트 없이 직접 만든 컨트롤러 인스턴스를 MockMvc에 물립
.setControllerAdvice() // 필요시 ControllerAdvice 추가
.build();
}
@Test
void saveTest() throws Exception {
// given
when(component.selectEvent("123")).thenReturn(456L);
// when & then
mockMvc.perform(post("/save")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"no\":\"123\"}"))
.andExpect(status().isOk());
verify(component).selectEvent("123");
}
}
✅ Inject를 받은 controller를 MockMvc를 만들 때 생성해줌
2. MockMvc content(dto)가 null로 들어감
ㄴ standaloneSetup()으로 만든 MockMvc는 스프링 MVC의 커스텀 ArgumentResolver / 메시지 컨버터 / Validator를 자동 등록하지 않음
ㄴ DTO는 @RequestModel이라는 커스텀 어노테이션(대개 내부에서 HandlerMethodArgumentResolver로 바인딩)을 통해 생성·바인딩되는데, 이 Resolver가 테스트에 등록되지 않아 컨트롤러 파라미터가 null 로 들어온 것. (@RequestBody가 아니라서 기본 @ModelAttribute 경로도 타지 않음
🔮 MockMvc가 컨트롤러에 파라미터를 넘기는 과정과 원리
1️⃣ MockMvc → DispatcherServlet 호출
ㄴ MockMvc는 실제 톰캣을 띄우지 않고 DispatcherServlet을 직접 호출
2️⃣ 핸들러 찾기
ㄴ HandlerMapping(ex. RequestMappingHandlerMapping)이 요청 URL/HTTP 메서드로 컨트롤러 메서드 찾기
ㄴ 예: @PostMapping("/saveEvtRequest") 메서드
3️⃣ 핸들러 실행 준비
ㄴ HandlerAdapter(ex. RequestMappingHandlerAdapter)가 컨트롤러 메서드를 호출 가능한 형태로 래핑
4️⃣ 메서드 파라미터 해석
ㄴ HandlerMethodArgumentResolverComposite가 파라미터마다 다음 규칙으로 “누가 처리할지”를 결정하고 값을 만든다
- @RequestBody가 붙어 있으면 → RequestResponseBodyMethodProcessor
- 담당 리졸버: RequestResponseBodyMethodProcessor
- 절차
- 요청의 Content-Type 확인 (예: application/json).
- 등록된 HttpMessageConverter 중 읽을 수 있는 것을 선택
- JSON이면 보통 MappingJackson2HttpMessageConverter가 선택되고 내부에서 ObjectMapper가 동작.
- 바디를 DTO로 역직렬화.
- @Valid가 있으면 Validator로 검증 → 오류시 예외.
- @ModelAttribute나 어노테이션 없는 POJO면 → ModelAttributeMethodProcessor/ServletModelAttributeMethodArgumentResolver
- 담당 리졸버: ModelAttributeMethodProcessor 등
- 절차
- request.getParameterMap()(쿼리스트링, 폼 urlencoded)을 키-값으로 읽음.
- WebDataBinder + ConversionService로 필드 매핑 & 타입 변환.
- @Valid가 있으면 Validator 검증.
- @PathVariable, @RequestParam, @RequestHeader 등은 각 전용 리졸버
- 커스텀 어노테이션(예: @RequestModel)이면 → **네가 등록한 HandlerMethodArgumentResolver**가 처리해야 함
5️⃣ 검증(@Valid/@Validated)
ㄴ 값이 만들어진 뒤 WebDataBinder가 붙고, 파라미터에 @Valid/@Validated가 있으면 등록된 Validator(대개 LocalValidatorFactoryBean)로 검증
ㄴ 오류가 나면 MethodArgumentNotValidException 등을 터뜨리거나, BindingResult 파라미터가 있으면 그쪽에 담김
@BeforeEach
void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(godEvtDctV3Controller)
.setCustomArgumentResolvers(new RequestModelArgumentResolver(...)) // ← 필수
.setMessageConverters(new MappingJackson2HttpMessageConverter()) // JSON 바디용
.setValidator(new LocalValidatorFactoryBean()) // @Valid
.build();
}
✅ 커스텀 리졸버 등록해주기
'SERVER > Architecture' 카테고리의 다른 글
| 프록시 서버 (0) | 2026.03.11 |
|---|---|
| Strangler Fig Pattern : 테스트 하지 않는 환경에서 TDD 흐름 만들기 (0) | 2026.01.02 |
| [MSA] 공통으로 사용하는 컴포넌트의 API 설계 방법 (1) | 2025.07.07 |
| [냄새라도 TDD/OOP] 2-2. 단위 테스트 (컨트롤러) (0) | 2025.07.04 |
| [냄새라도 TDD/OOP] 3. 통합테스트 (서비스) (0) | 2025.06.27 |