본문 바로가기
날것 그대로의 공부기록/날 것 그대로 일지

컨트롤러만 테스트하기

by 20j 2025. 8. 4.

요약

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler' 에 대해서

  • 원인

애플리케이션 시작 시 설정된 어노테이션이 문제를 일으킴

  • main application에 붙은 어노테이션을 분리
@SpringBootApplication
@EnableJpaAuditing //분리
@EnableRetry //필요
public class AppApplication {

    public static void main(String[] args) { ... }
  • 분리 예시
@Profile("!test")
@Configuration
@EnableJpaAuditing
@EnableRetry
public class BaseConfig {

}

Spring Security 적용 시 401 문제

  • 원인

@WebMvcTest 를 사용할 때 명시적으로 security config bean을 Import하지 않으면, 기본 스프링 시큐리티 구현체가 동작

  • Spring Security Filter chain 구현체를 명시적으로 Import 해주기
@Import(WebTestConfig.class) 
@ActiveProfiles("test")
@WebMvcTest(controllers = OfflineMemberController.class)
class OfflineMemberControllerTest { ... }
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebTestConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth ->
                auth.anyRequest().permitAll()
            );
        return http.build();
    }
}

0. 서론

@WebMvcTest를 이용해서 컨트롤러의 동작만 테스트하고 싶다.

하지만 지속적으로 오류가 발생하였다.

현재 구성

@WebMvcTest(OfflineMemberController.class)
class OfflineMemberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private MemberComponentService memberComponentService;

    @Test
    @DisplayName("오프라인 회원 등록페이지 정상 조회")
    void test1() throws Exception {
        mockMvc.perform(get("/member"))
            .andExpect(status().isOk())
            .andExpect(view().name("member"))
            .andExpect(model().attributeExists("memberForm"));
    }

}

error 로그

java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@a99c42c testClass = ..

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': JPA metamodel must not be empty
	a
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty

1. 원인 분석

현재 Application 시작 부분에는 JpaAuditing이 적용되어있는데 이로 인해서 문제가 발생하는 것 아닐까?

→ 로그를 확인하면, JPA 관련 문제라고 추측되기 때문

@SpringBootApplication
@EnableJpaAuditing
@EnableRetry
public class AppApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }
}

1-2. 시도 : Profile로 시작 지점 변경하기

5:15:17.668 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [~~Test]: OfflineMemberControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
15:15:17.897 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration ~~Application for test class ~~Test
15:15:18,318 |-WARN in ch.qos.logback.core.joran.action.ConversionRuleAction - [converterClass] attribute is deprecated and replaced by [class]. See element [conversionRule] near line 5
15:15:18,354 |-INFO in ch.qos.logback.core.model.processor.ConversionRuleModelHandler - registering conversion word clr with class [org.springframework.boot.logging.logback.ColorConverter]
1

애플리케이션 시작 시 로그를 확인해보면 configuration class를 먼저 찾고,

탐색에 실패하여 메인 Application 시작지점으로 가고 있다.

따라서, test 경로 내에 Profile을 나누어서 JpaAuditing을 사용하지 않는 진입지점을 만들면 해결될 수 있지 않을까?

특히 기존의 코드를 건들이기 보다는 유지하면서 테스트 환경만 분리해내고 싶었다.

→ 실패. 새로운 진입 지점을 만든다는 것이 어떤식으로 구성되어야할지 모르겠다.

1-3. web관련 bean만 뜨지만, 다른 빈을 필요로 한다.

Positive matches:
-----------------

   ErrorMvcAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'jakarta.servlet.Servlet', 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
      - found 'session' scope (OnWebApplicationCondition)

   ErrorMvcAutoConfiguration#basicErrorController matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorController; SearchStrategy: current) did not find any beans (OnBeanCondition)

   ErrorMvcAutoConfiguration#errorAttributes matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorAttributes; SearchStrategy: current) did not find any beans (OnBeanCondition)

   HttpEncodingAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.web.filter.CharacterEncodingFilter' (OnClassCondition)
      - found 'session' scope (OnWebApplicationCondition)
      - @ConditionalOnProperty (server.servlet.encoding.enabled) matched (OnPropertyCondition)

   HttpEncodingAutoConfiguration#characterEncodingFilter matched:
      - @ConditionalOnMissingBean (types: org.springframework.web.filter.CharacterEncodingFilter; SearchStrategy: all) did not find any beans (OnBeanCondition)

   HttpMessageConvertersAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.converter.HttpMessageConverter' (OnClassCondition)
      - NoneNestedConditions 0 matched 1 did not; NestedCondition on HttpMessageConvertersAutoConfiguration.NotReactiveWebApplicationCondition.ReactiveWebApplication did not find reactive web application classes (HttpMessageConvertersAutoConfiguration.NotReactiveWebApplicationCondition)

해당 로그를 확인하면, web관련 bean만 생성되었지만,

해당 Bean에 대한 요구가 어디선가 존재해서 문제가 생기는 듯 하다.

이때는 Mock된 Service에서 의존성이 발생하여 문제가 생기는 것은 아닐까? 라는 의심이 생겼다.

1-4. Mockito 작성 방법이 잘못되었는가?

Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build what is described in Mockito's documentation: <https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3>

해당 로그에 따르면?

0.3. Explicitly setting up instrumentation for inline mocking (Java 21+) Link icon
Starting from Java 21, the JDK restricts the ability of libraries to attach a Java agent to their own JVM. As a result, the inline-mock-maker might not be able to function without an explicit setup to enable instrumentation, and the JVM will always display a warning. The following are examples about how to set up mockito-core as a Java agent, and it may be more appropriate to choose a different approach depending on your project constraints.
To explicitly attach Mockito during test execution, the library's jar file needs to be specified as -javaagent as an argument to the executing JVM. To enable this in Gradle, the following example adds Mockito to all test tasks using Kotlin DSL. Although omitted for simplicity, using a CommandLineArgumentProvider is recommended by Gradle to ensure task relocatability (documentation):

https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html

agent 설정을 해주라고? 한다.

→ (상관없는 듯 하다. 일단 넘어감)

1-5. AOP를 통한 로깅 기능에서 원인 발생?

컨트롤러의 접근이 발생할 때 aop를 통해 로깅기능이 동작할 수 있도록 설정해두었다.

처음 서버를 실행시킬 때 logger 관련 설정들이 초기화되는 것을 보니 로그 관련 빈들이 필요하게 되지만, 해당 빈을 찾지 못하여서 오류가 발생하는 것 아닐까?

→ test profile에서 aop 관련 설정을 동작하지 않도록 해보았지만 해결안됨.

2. 해결 - Configuration 설정 분리

원래 MainApplication에

@SpringBootApplication
//@EnableJpaAuditing
//@EnableRetry
public class AppApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }

}

되어있는 설정을 분리하여 config 클래스를 새로 만들고 테스트 시에는 동작하지 않도록하여 해결하였다.


3. 새로운 문제 : security 401 response

분명 SecurityConfig에 test에는 적용되지 않도록 명시해두었지만 401반환이 발생한다.

내 생각에는, Security 설정이 구성되어있지않으면 기본으로 Security 구현체가 동작하게되고 모든 요청에 대해서 차단되는 것이라고 생각되어진다.

따라서 test전용 Security Config를 만들어서 해결하거나 Security 인증이 된 것

@TestConfiguration
@ActiveProfiles("webtest")
public class WebTestConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .cors(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth ->
                auth.anyRequest().permitAll()
            );
        return http.build();
    }
}

test 전용 securityFilterChain 을 구성하였지만 그대로 401 error가 반환되고 있다.

[2025-07-01 16:41:05:2810]  WARN  24464 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: f3b92ffa-910c-45ab-ba58-b0d67d7ef7c3

This generated password is for development use only. Your security configuration must be updated before running your application in production.

로그를 보면, security 설정이 없을 때 자동으로 생성해주는 security 구현체를 사용하고 있는 것으로 추정된다.


3-1. 원인 분석

내 생각에는, webmvctest 어노테이션으로 인해 컨트롤러 관련 빈만 띄워지고 있지만, configuration 어노테이션으로 지정된 security 설정 관련 빈들은 컨트롤러 관련 빈이 아니라서 생성이 안되는 듯하다.

스프링 시큐리티는 스프링 컨테이너 외부에 위치하고 있으므로 빈으로 관리받지 않게되어 기본적으로 작동하고, 내부에 구현체관련 bean은 존재하지 않으므로 기본 구현체들을 통해서 동작하여 401을 반환한다고 생각되어진다.

@Autowired
    private ApplicationContext context;

    @Test
    void printAllBeans() {
        Arrays.stream(context.getBeanDefinitionNames())
            .sorted()
            .forEach(System.out::println);
    }

가볍게 생성된 빈들 이름을 출력하게 하여 확인해보니

org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration
org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration
org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity
org.springframework.security.config.annotation.web.configuration.ObservationConfiguration
org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration

시큐리티 관련 빈들이 생성되었으며 이때 내가 커스텀한 WebTestConfig 빈은 생성되지 않았다.

 

이때 spring boot test로 동작하면

다음과 같이 볼 수 있다.


4.결론

@WebMvcTest 를 사용할 때 명시적으로 security config bean을 Import하지 않으면, 기본 스프링 시큐리티 구현체가 동작한다.

WebMvcTest는 컨트롤러 관련 Bean만 띄우게 되고, security config bean은 컨트롤러 관련 빈이 아니라서 생성되지 않는다.

하지만 스프링 시큐리티는 필터체인으로 스프링 컨테이너 외부에 위치하고, 내부의 bean을 프록시 방식으로 호출하여 사용하기에

WebMvcTest 어노테이션이 적용되더라도 동작하게 되고, 내부 커스텀 빈이 없으므로 기본 구현체를 생성해서 동작하게 된다.


해결

Import 어노테이션으로 Security 설정을 추가해주어 정상적인 테스트 완료