[트러블 슈팅 - Spring Test] 의존성 주입 실패 (test yml 파일 안 읽힘)

2024. 8. 25. 20:35Spring

 

문제 사항

 

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'messageController' defined in file [~~~\controller\MessageController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'messageService': Injection of autowired dependencies failed

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageService': Injection of autowired dependencies failed

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'naver-cloud-sms.accessKey' in value "${naver-cloud-sms.accessKey}"

 

  • MessageController 빈 등록이 실패했다. MessageService의 의존성 주입 실패로 인한 문제이다.
  • 구체적인 원인은 application-api.properties 파일에 정의된 설정 값(naver-cloud-sms.accessKey)이 읽히지 않아 @Value로 주입되는 값이 없어서 발생한 문제이다.

 

문제 해결

 

 

원인

 

  • API 설정 파일이 읽히지 않음: application-api.properties 파일이 제대로 로드되지 않아 스프링이 @Value로 주입하려는 값을 찾지 못함.
  • 테스트 환경에서의 프로퍼티 파일 로드 문제: 테스트 환경에서는 기본적으로 application.yml 파일 이외 파일은 읽지 못하는 문제가 있다 때문에 테스트를 위한 properties 파일 경로를 별도 설정해 읽을 수 있게 설정해 둔 다.
  • 통합 테스트의 특성: @SpringBootTest는 통합 테스트로 모든 빈을 로드합니다. 이 과정에서 다른 부분(이 경우 MessageService)에서 발생한 문제 application-api.properties의 등록된 @Value 값을 읽어 오지 못한 문제가 전체 테스트에 영향을 미쳐 사용하지 않은 MessageController에서도 오류가 발생했습니다.

문제 해결

  1. 프로퍼티 파일 경로 명시: @TestPropertySource를 사용해 application-api.properties 파일의 경로를 명시적으로 지정했습니다. 이를 통해 테스트 시 필요한 프로퍼티 값이 올바르게 로드되도록 했습니다.
  2. BaseTest 추상 클래스 도입: 공통적으로 필요한 설정과 프로퍼티 파일 로드를 BaseTest라는 추상 클래스로 묶어 관리하도록 했습니다. 이로 인해 각 테스트 클래스에서 반복적으로 설정을 지정하지 않아도 되며 설정 파일 누락 문제를 방지할 수 있습니다.

 

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class MessageService {

    @Value("${naver-cloud-sms.accessKey}")
    private String accessKey;

    @Value("${naver-cloud-sms.secretKey}")
    private String secretKey;

    @Value("${naver-cloud-sms.serviceId}")
    private String serviceId;

    @Value("${naver-cloud-sms.senderPhone}")
    private String phone;

    private final StoreRepository storeRepository;
    private final MessageStorageRepository messageStorageRepository;
    private final MessageHistoryRepository messageHistoryRepository;
    
}

 

@SpringBootTest
@TestPropertySource(locations = "classpath:application-api.properties")
@ActiveProfiles("test")
@Transactional
public abstract class BaseTest {
    // 공통 설정 및 프로퍼티 파일 경로 지정
}

// 개별 테스트 클래스
@Slf4j
class MembersServiceTest extends BaseTest {
    @Autowired
    private MembersService membersService;

    @Autowired
    private MembersRepository membersRepository;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @BeforeEach
    @DisplayName("테스트용 아이디 생성")
    void ApprovedID() {
        Members members = Members.builder()
                .memberId("UserId")
                .memberPassword(encoder.encode("UserPw"))
                .role(Role.USER)
                .build();

        membersRepository.save(members);
        log.info("테스트용 아이디 생성 완료");
    }

    @Test
    @DisplayName("아이디 생성")
    void createMember() {
        //given
        MembersDTO membersDTO = new MembersDTO("createId", "createPw");

        //when
        Boolean result = membersService.createMember(membersDTO);

        //then
        assertEquals(result, true);
    }
}

 

 

 

왜 사용하지 않은 MessageService에서 오류가 발생했는가?

 

@SpringBootTest는 통합 테스트 방식으로 테스트 실행 시 전체 애플리케이션 컨테이너에 등록된 context를 로드합니다. 이 과정에서 MessageService와 같은 다른 빈들도 함께 로드되므로 그 빈에서 발생한 문제가 전체 context 로딩 실패로 이어져 테스트 실행이 중지될 수 있습니다.

 

결론

 

@SpringBootTest 사용 시 주의사항: 통합 테스트에서 전체 빈이 로드되므로 테스트와 직접적으로 관련 없는 빈에서 발생한 문제도 테스트에 영향을 줄 수 있습니다. 이를 방지하기 위해 @MockBean을 사용하거나, 필요한 빈만 로드하는 테스트 방@WebMvcTest을 사용하는 방법이 있습니다.