TIL [20240614] - AOP Log, Mock Test 문제 해결

이윤성·2024년 6월 14일
0

TIL

목록 보기
38/51

오늘은 특정 키워드를 정리한 것이 아닌 문제 해결 과정을 적었습니다.

❗ AOP 관련 오류

Cannot invoke "com.sparta.fifteen.service.UserService.registerUser(
	com.sparta.fifteen.dto.UserRegisterRequestDto)" because "this.userService" is null

이건 뭐지?

AOP를 테스트 하려던 나에게 시작부터 문제가 생겼다.

AI 답변은 크로스체크가 필수다. 주석처리해서 돌려보면 잘 돌아간다. 그래서 추궁 시작..

해결법을 보니 이번 문제는 쉽게 퍼져있는 문제가 아닌 듯했다. AI는 이제 무시하고 직접 발로 뛰자

https://stackoverflow.com/questions/55686489/autowire-returns-null-after-adding-spring-aop

Spring AOP는 기본적으로 프록시를 사용하여 동작합니다. 이 경우에는 인터페이스가 구현되지 않았기 때문에 클래스 기반 프록시가 사용되고 있습니다. 클래스 기반 프록시는 실제 클래스를 상속하고 모든 메서드를 오버라이드하여 인터셉터/애스펙트를 적용합니다.
그러나 private 메서드는 하위 클래스에서 오버라이드될 수 없으므로 컨트롤러 메서드는 프록시된 객체 대신 프록시에서 호출됩니다. 프록시에는 주입된 것이 없으므로 aService 필드는 항상 null입니다.
이를 해결하려면 메서드를 public 또는 protected로 만들어 하위 클래스에서 메서드를 오버라이드할 수 있도록 하세요. 그러면 최종적으로 프록시 대신 프록시된 인스턴스에서 메서드가 호출됩니다.

그렇다고 한다. 메서드에 private 사용이 문제가 됐던 것이다.

https://www.jianshu.com/p/917da97c96bd 유사 사례가 하나 더있다.
중국 사이트니 들어가는게 꺼려지는 사람은 안들어가도 된다.

❗ Mock Test 오류

MockMvc test를 하는데 뭐가 있는지를 모르니 막막했지만 검색해보며 코드를 작성했다.

@WebMvcTest(UserController.class)
@MockBean(JpaMetamodelMappingContext.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @MockBean
    private AuthenticationService authenticationService;

    @Autowired
    private ObjectMapper objectMapper;
    @Test
    @DisplayName("가입")
    void testSignup() throws Exception{
        // given
        UserRegisterRequestDto requestDto = new UserRegisterRequestDto();
        requestDto.setUsername("testUser");
        requestDto.setPassword("abcd1234");
        requestDto.setEmail("test@example.com");
        requestDto.setOneLine("hi");

        UserRegisterResponseDto responseDto = UserRegisterResponseDto.builder()
                .id(1L)
                .username("testUser")
                .email("test@example.com")
                .build();

        when(userService.registerUser(any(UserRegisterRequestDto.class))).thenReturn(responseDto);

        //when
        ResultActions resultActions = mockMvc.perform(post("/api/user")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(requestDto)));

        //action
        resultActions.andExpect(status().isOk())
                .andExpect(content().json(objectMapper.writeValueAsString(responseDto)));
    }
}

403 Forbidden 에러가 발생했다.

Mockito는 테스트 대상 코드를 다른 환경이나 의존성으로부터 격리시켜 독립적으로 테스트를 할 수있게 해준다. 
그래서 테스트 대상 코드에서 의존하는 객체를 가짜(Mock) 객체로 대체한다.

이러다보니 보안 및 필터 관련 설정이 작동하지않는 것으로 확인하고
perform 코드에 csrf() 비활성화를 하기위해 작업을 진행했다.

ResultActions resultActions = mockMvc.perform(post("/api/user")
				.with(csrf())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(requestDto)));

해당 코드를 사용하기 위해서는 spring security test를 gradle에 등록해야한다.

testImplementation 'org.springframework.security:spring-security-test'

이러고 잘되겠지했는데 401 Unauthorized 오류가 발생하여 해결법을 찾아본 뒤@AutoConfigureMockMvc(addFilters = false)를 추가했다. Spring security filter를 비활성화 하는 것인데 필터 구현된 것을 끌어오는 건 Mockito 테스트가 의도하는 바와 맞지않는다고 생각하여 이렇게 진행했다.

📚 오늘의 회고

프로젝트마다 의도치않은 이유로 문제가 발생할 수 있다는 것을 깨달았다. private가 AOP와 연관있을 줄은 파악하지 못했다. 이전 팀원들도 같은 문제가 생겼다고 연락이 오길래 내가 찾은 해결법을 공유해서 해결하였다.

0개의 댓글