Spring/Spring Boot

[Spring Boot][Test] 6. Test

noahkim_ 2025. 4. 21. 02:17

1. Test Scope Dependencies

spring-boot-starter-test에 포함된 주요 라이브러리

라이브러리 설명
JUnit 5
Java 애플리케이션 단위 테스트의 표준 프레임워크
Spring Test & Spring Boot Test
Spring Boot 애플리케이션을 위한 테스트 유틸리티 및 통합 테스트 지원
AssertJ
직관적이고 유창한 API를 제공하는 assertion 라이브러리
Hamcrest
matcher 객체(제약조건 또는 조건자)를 제공하는 라이브러리
Mockito Java용 목(mock) 객체 생성 프레임워크
JSONassert
JSON 형식 데이터를 위한 assertion 라이브러리
JsonPath JSON 데이터를 위한 XPath 유사 쿼리 언어
Awaitility 비동기 시스템을 테스트하기 위한 라이브러리

 

2. @SpringBootTest

  • Spring Boot 애플리케이션 컨텍스트를 로드하여 통합 테스트를 가능하게 해주는 애노테이션
    • @ContextConfiguration의 대안
    • @ExtendWith(SpringExtension.class)가 포함되어 있음
  • 실제 환경과 유사하게 통합 테스트하고 싶은 경우

 

옵션

webEnvironment
옵션 설명
MOCK (기본값)
웹 ApplicationContext를 로드하되, 서블릿 컨테이너는 시작하지 않음 (Mock 환경에서 테스트)
- @AutoConfigureMockMvc 또는 @AutoConfigureWebTestClient와 함께 사용
RANDOM_PORT 실제 내장 서버 시작, 랜덤 포트에서 실행됨
- @Transactional 시 롤백되지 않음 (클라이언트/서버가 다른 스레드)
DEFINED_PORT
실제 내장 서버 시작, 지정 포트(application.properties) 또는 기본 포트(8080) 사용
- @Transactional 시 롤백되지 않음 (클라이언트/서버가 다른 스레드)
NONE 웹 환경 없이 일반 ApplicationContext만 로드

 

예제) MOCK

더보기
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = org.example.Main.class)
@AutoConfigureMockMvc
public class WebEnvironmentMockTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testHello() throws Exception{
        mockMvc.perform(get("/test/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("hello spring"));
    }
}
2025-04-21T05:33:09.611+09:00  INFO 97099 --- [    Test worker] WebEnvironmentMockTest                   : Started WebEnvironmentMockTest in 3.118 seconds (process running for 4.144)

 

예제) RANDOM_PORT

더보기
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = org.example.Main.class)
public class WebEnvironmentRandomPortTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testHello() {
        String response = restTemplate.getForObject("http://localhost:"+port+"/test/hello", String.class);
        assertEquals("hello spring", response);
    }
}
2025-04-21T05:42:18.160+09:00  INFO 3278 --- [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 61220 (http) with context path ''

 

예제) DEFINED_PORT

더보기
# application-test.yml (/src/test/resources)
server:
  port: 8085
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = org.example.Main.class)
@ActiveProfiles("test")
public class WebEnvironmentDefinedPortTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testHello() {
        int port = 8085;
        String response = restTemplate.getForObject("http://localhost:"+port+"/test/hello", String.class);
        assertEquals("hello spring", response);
    }
}
2025-04-21T05:45:19.136+09:00  INFO 5458 --- [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8085 (http) with context path ''

 

useMainMethod
실행 모드 설명
ALWAYS 무조건 main() 메서드를 실행함
WHEN_AVAILABLE main() 메서드가 있으면 실행하고, 없으면 실행하지 않음
NEVER (기본값) main() 메서드를 절대 실행하지 않음

 

예제) ALWAYS

더보기
@SpringBootApplication
public class  Main {
    public static void main(String[] args) {
        System.out.println("ALWAYS execute!");

        SpringApplication app = new SpringApplication(Main.class);
        app.run(args);
    }
}
@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.ALWAYS, classes = org.example.Main.class)
public class UseMainMethodAlwaysTest {

    @Test
    void name() {
        assertTrue(true);
    }
}

ALWAYS execute! 출력 ✅

 

예제) NEVER

더보기
@SpringBootApplication
public class  Main {
    public static void main(String[] args) {
        System.out.println("ALWAYS execute!");

        SpringApplication app = new SpringApplication(Main.class);
        app.run(args);
    }
}
@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.NEVER, classes = org.example.Main.class)
public class UseMainMethodAlwaysTest {

    @Test
    void name() {
        assertTrue(true);
    }
}

ALWAYS execute! 출력 ❌

 

3. @TestConfiguration

  • 테스트 전용 설정을 위한 클래스
  • 테스트 환경에서만 활성화됨
  • 중첩하여 사용할 수 있음
항목 @Configuration
@TestConfiguration
정의 위치 일반 애플리케이션 코드 테스트 코드 전용
역할 Spring에서 애플리케이션의 Bean 설정을 정의
테스트에서만 사용할 설정 및 Bean 정의
컴포넌트 스캔 여부 기본적으로 컴포넌트 스캔 대상이 됨
테스트에서 자동으로 스캔되지 않음
(@SpringBootTest, @DataJpaTest 등)
주요 용도 실제 어플리케이션의 설정 클래스
테스트 전용 Bean 구성 (Mock, Stub 등)
컨텍스트 통합 여부 @ComponentScan 등에 의해 전체 Context에 포함
분리된 테스트 Context에서만 사용됨

 

 

예시) @TestConfiguration

더보기

1. 중첩

@SpringBootTest(classes = {org.example.Main.class, TestConfigurationTest.TestConfig.class})
public class TestConfigurationTest {

    @Autowired
    private TestService myService;  // 실제 빈을 테스트할 대상

    @TestConfiguration
    static class TestConfig {
        @Bean
        public TestService myService() {
            return mock(TestService.class);  // 테스트용 서비스 빈
        }
    }

    @Test
    void testService() {
        assertNotNull(myService);  // 테스트 코드
    }
}

 

2. @Import

@TestConfiguration
public class TestConfig {

    @Bean
    public TestService myService() {
        return mock(TestService.class);  // 테스트용 서비스 빈
    }
}
@SpringBootTest(classes = org.example.Main.class)
@Import(TestConfig.class)
public class TestConfigurationTestWithImport {

    @Autowired
    private TestService myService;  // 실제 빈을 테스트할 대상

    @Test
    void testService() {
        assertNotNull(myService);  // 테스트 코드
    }
}

 

4. Slice Test

  • 테스트 시 전체 애플리케이션을 로드하지 않고, 특정 레이어만 테스트할 수 있도록 다양한 @…Test 애노테이션을 제공
  • 여러 슬라이스 테스트 애노테이션을 동시에 사용 불가 (@SpringBootTest + @AutoConfigure… 조합으로 대체)
  • 슬라이스 테스트 (@WebMvcTest, @DataJpaTest 등)는 기본적으로 @Configuration 클래스는 스캔 안 함.
    • 하지만 @Component는 스캔 대상이 됨.

 

 

 

종류

애노테이션 자동 구성 대상 목적 및 특징 기타 비고
@WebMvcTest @Controller, 
@ControllerAdvice
@JsonComponent
Converter
GenericConverter
Filter
HandlerInterceptor
WebMvcConfigurer
HandlerMethodArgumentResolver
Spring MVC Web Layer 테스트
MockMvc와 함께 사용
@WebFluxTest @Controller
@ControllerAdvice (WebFlux)
Reactive Web Layer 테스트
WebClient 테스트용
@DataJpaTest JPA 관련 컴포넌트 Repository, EntityManager 테스트
기본적으로 내장 H2 사용
기본적으로 트랜잭션이 적용됨
@DataJdbcTest Spring Data JDBC JDBC 기반 Repository 테스트 -
@DataMongoTest MongoDB 관련 구성 MongoRepository 테스트 -
@DataRedisTest  @RedisHash
Spring Data Redis Repository
Redis Repository 테스트
Spring Boot 2.4+부터 정식 지원
@Component, @ConfigurationProperties는 제외됨
@RestClientTest @RestTemplateBuilder
MockRestServiceServer
REST 클라이언트 테스트 (외부 API Mocking)
외부 HTTP 호출 테스트용
@JdbcTest JDBC 관련 구성 JDBC 테스트, 트랜잭션 포함
@DataJdbcTest보다 더 범용적으로 사용 가능
@JsonTest ObjectMapper
JsonComponent
JSON 직렬화/역직렬화 테스트
Jackson 관련 테스트

 

예제) @WebMvcTest

더보기
@WebMvcTest(
    controllers = TestController.class,
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = JsonComponent.class),
        @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org\\.example\\..*Config"),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = jakarta.servlet.Filter.class) // 일반 필터
    }
)
@ContextConfiguration(classes = Main.class)
public class ControllerSlicingTest {

    @Autowired
    private MockMvc mockMvc;


    @Test
    void testHello() throws Exception {
        mockMvc.perform(get("/test/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("hello spring"));
    }
}

 

예제) @DataJpaTest

더보기
@DataJpaTest
@TestPropertySource(locations = "classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(classes = Main.class)
public class MemberRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void testUserRepository() {
        User user = new User("dummy user");
        userRepository.save(user);

        assertThat(userRepository.findByName("dummy user")).isNotNull();
    }
}

 

@AutoConfigureTestDatabase

  • 테스트 시, 기본 데이터베이스는 내장 H2로 지정됨
  • 테스트 시 사용할 데이터베이스 설정을 셋팅할 수 있음

 

예제) @DataJdbcTest

더보기
@DataJdbcTest
@TestPropertySource(locations = "classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(classes = Main.class)
public class JdbcSlicingTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    void testUserTable() {
        jdbcTemplate.update("INSERT INTO user(name) VALUES(?)", "dummy user");

        User newUser = jdbcTemplate.queryForObject("SELECT * FROM user WHERE name='" + "dummy user" + "'", (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            return user;
        });

        assertEquals(newUser.getName(), "dummy user");
    }
}

 

예제) @DataMongoTest

 

예제) @DataRedisTest

 

예제) @RestClientTest

더보기
@Service
public class MyRestClient {
    private final RestTemplate restTemplate;

    public MyRestClient(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    public String getData() {
        URI uri = UriComponentsBuilder.fromUriString("http://localhost:8080").path("/hello").encode().build().toUri();
        return restTemplate.getForObject(uri, String.class);
    }
}
@RestClientTest(MyRestClient.class)
@ContextConfiguration(classes = Main.class)
public class RestSlicingTest {

    @Autowired
    private MyRestClient myRestClient;

    @Autowired
    private MockRestServiceServer mockServer;

    @Test
    void testRestClient() {
        mockServer.expect(requestTo("http://localhost:8080/hello")).andRespond(withSuccess("Mocked Response", MediaType.TEXT_PLAIN));

        assertThat(myRestClient.getData()).isEqualTo("Mocked Response");
        mockServer.verify();
    }
}

MockRestServiceServer

  • RestTemplate 또는 RestClient의 HTTP 요청을 가로채서 모의 응답을 반환합니다.
  • 실제 네트워크 통신 없이 메모리 내에서 모든 처리가 완료됩니다.

 

예제) @JsonTest

더보기
@JsonTest
@ContextConfiguration(classes = Main.class)
public class JsonSlicingTest {

    @Autowired
    private JacksonTester<User> json;

    @Test
    void testJsonSerialization() throws IOException {
        String content = "{\"name\":\"json\"}";

        assertThat(json.parseObject(content).getName()).isEqualTo("json");
    }
}

 

Auto-Configuration 추가하기

방법 적용 방식 적용 범위
@ImportAutoConfiguration 테스트 클래스에서 직접 어노테이션 사용 
(해당 테스트 클래스에만 적용)
@ImportAutoConfiguration 을 써야 Auto-Configuration이 적용됨.
(@Import ❌)
META-INF/spring/*.imports 리소스 파일에 클래스명 명시
(프로젝트 내 모든 해당 슬라이스 테스트에 전역 적용)
프로젝트 전체에 일괄 적용
관리가 쉽고, 일관성 유지 용이

 

예제) MyJdbcTest

더보기
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {
}

 

예제) META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports

더보기
com.example.IntegrationAutoConfiguration

 

 

출처