하위 태스크 1

테스트 의존성 확인

spring-boot-starter-test 의존성 확인

build.gradle 파일의 dependencies 영역에 테스트 관련 의존성을 추가한다.

dependencies {
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

하위 태스크 2 ~ 3

통합 테스트 클래스 생성

@SpringBootTest + @AutoConfigureMockMvc 테스트 클래스 생성

기본 GET API 테스트

/members 등 엔드포인트에 대한 200 OK 테스트 작성

MemberController 클래스를 테스트하는 MemberControllerTests 클래스를 작성한다.

  • @SpringBootTest: 클래스가 테스트에 포함되고 내부 메서드 중에 @Test 어노테이션이 추가된 메서드가 실행된다.
  • @AutoConfigureMockMvc: MockMvc를 활성화한다. MockMvc는 WAS를 모방하여 스필링 컨테이너를 만들고 테스트를 위해 REST API를 호출한다.
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("사용자 컨트롤러 테스트")
public class MemberControllerTests {
    @Autowired
    private MockMvc mockMvc;
 
    @Autowired
    private ObjectMapper objectMapper;
 
    @Test
    @DisplayName("사용자 생성 테스트")
    public void create() throws Exception {
        MemberRequest memberRequest = MemberRequest.builder()
                .name("홍길동")
                .email("[email protected]")
                .age(32)
                .build();
 
        RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/members")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .accept(MediaType.APPLICATION_JSON_VALUE)
                .content(objectMapper.writeValueAsString(memberRequest));
 
		// 200 OK 테스트
        MvcResult mvcResult = mockMvc.perform(requestBuilder)
                .andExpect(status().is2xxSuccessful())
                .andReturn();
 
        MemberResponse memberResponse = objectMapper
                .readValue(mvcResult.getResponse().getContentAsByteArray(), MemberResponse.class);
                
		// 응답 데이터 테스트
        assertThat(memberResponse).isNotNull();
        assertThat(memberResponse.getId()).isGreaterThan(0);
        assertThat(memberResponse.getName()).isEqualTo("홍길동");
    }
}

하위 태스크 4 ~ 5

404/400 에러 테스트

잘못된 요청 시 404/400을 기대하는 테스트 추가

BDD 스타일 네이밍

Given-When-Then 구조의 테스트 메서드 이름/주석 작성

build.gradle 파일에 유효성 검증을 위한 의존성을 추가한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
+   implementation 'org.springframework.boot:spring-boot-starter-validation'
	// ...
}

MemberRequest 클래스와 MemberController 클래스의 멤버에 유효성 검증 어노테이션을 추가한다.

MemberRequest.java:

public class MemberRequest {
+   @NotBlank
    private String name;
 
+   @NotBlank
+   @Email
    private String email;
 
+   @Min(0)
    private Integer age;
}

MemberController.java:

public class MemberController {
	// ...
 
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
-   public MemberResponse post(@RequestBody MemberRequest memberRequest) {
+   public MemberResponse post(@Valid @RequestBody MemberRequest memberRequest) {
        return memberService.create(memberRequest);
    }
 
	// ...
}
 

MemberControllerTests 클래스에 잘못된 요청 시 404, 400을 기대하는 테스트를 추가한다.

public class MemberControllerTests {
	// ...
	
+   @Test
+   @DisplayName("존재하지 않는 ID가 존재하면 404 반환")
+   public void getNotFoundMember() throws Exception {
+       // Given
+       Long testId = -1L;
+
+       // When
+       RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/members/{id}", testId);
+
+       // Then
+       mockMvc.perform(requestBuilder)
+               .andExpect(status().isNotFound());
+   }
+
+   @Test
+   @DisplayName("유효성 검증에 실패하면 400 반환")
+   public void createBadRequestMember() throws Exception {
+       // Given
+       MemberRequest testRequest = MemberRequest.builder()
+               .name("")
+               .email("It is not a email.")
+               .age(-1)
+               .build();
+
+       // When
+       RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/members")
+               .contentType(MediaType.APPLICATION_JSON_VALUE)
+               .content(objectMapper.writeValueAsString(testRequest));
+
+       // Then
+       mockMvc.perform(requestBuilder)
+               .andExpect(status().isBadRequest());
+   }
}

MemberControllerTests 테스트를 실행한 결과는 다음과 같다.

하위 태스크 6

LoggingFilter 구현

OncePerRequestFilter 기반 로깅 필터 구현

OncePerRequestFilter 클래스를 상속받는 LoggingFilter 클래스를 생성한다.

@WebFilter(urlPatterns = "/api/*")
@Slf4j
public class LoggingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("요청: {}, {}", request.getMethod(), request.getRequestURI());
 
        filterChain.doFilter(request, response);
 
        log.info("응답: {}", response.getStatus());
    }
}

하위 태스크 7

FilterRegistrationBean 등록

필터 Bean 등록 및 URL 패턴/순서 지정

필터를 원하는 대로 제어하기 위한 FilterConfig 클래스를 생성한다.

@Configuration
public class FilterConfig {
 
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
 
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(1);
 
        return registrationBean;
    }
}

하위 태스크 8

필터 로그 검증

API 호출 시 필터 로그가 의도대로 출력되는지 확인

MemberControllerTests 테스트를 수행한다. 로깅 필터가 동작하여 테스트 로그에 기록된 것을 확인할 수 있다.