본문 바로가기

Springboot

[spring boot] Test

 

의존성을 확인하자

spring-boot-starter-test가 test 기능을 해준다.

scope은 test로 지정하면 된다.

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

test 만들기 단축키: Alt+Insert

 

@SpringBootTest

@SpringBootTest는 Spring main application, 즉 @SpringBootApplication을 찾아가서 이하의 모든 빈을 스캔하고 test용 application에 등록한다.

어마어마한 통합테스트인 것이다.

그리고 밑에 나오지만, MockBean만 교체해준다.

가장 기본적인 테스트 형태는 아래와 같다.

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest // (webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class SampleControllerTest {

}

@SpringBootTest는 @RunWith(SpringRunner.class)와 같이 써야 한다.

@SpringBootTest의 default 웹 환경 설정값은 MOCK 이다.

 

웹 환경 설정: MOCK

웹 환경 설정의 기본값이다.

내장 톰캣을 구동하지 않는다.

Mocking한 DispatcherServlet을 사용해서 테스트 하겠다는 뜻이다.

하지만 mock up된 서블릿과 interaction을 하려면, MockMvc라는 클라이언트를 꼭 사용해야 한다.

@AutoConfigureMockMvc을 이용하는 것이 MockMvc를 이용하는 가장 간단한 방법이다.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest // (webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception{
        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk()) // 200이길 바라고
                .andExpect(content().string("hello jueun")) // 내용이 이거 이고
                .andDo(print()); // 요청 온 것을 찍어줬음 좋겠다.
    }
}

웹 환경 설정: RANDON_PORT, DEFINED_PORT

RANDON_PORT: 내장 톰캣 사용하여 test용 서블릿이 랜덤한 포트에 뜬다.

DEFINED_PORT: 내장 톰캣 사용하여 test용 서블릿이 지정한 포트에 뜬다.

톰캣 서버에서 요청을 보내고 응답을 받아 확인하고 있다.

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

class SampleControllerTest {

    @Autowired // test용 RestTemplate
    TestRestTemplate testRestTemplate;

    @Test
    public void hello() throws Exception{
        // 이 body type에 있는 객체를 받음.
        String result = testRestTemplate.getForObject("/hello", String.class);
        assertThat(result).isEqualTo("hello jueun");
    }
}

웹 환경 설정: NONE

NONE은 서블릿 환경 제공하지 않는다는 의미이다.

@MockBean

ApplicationContext에 들어있는 빈을 Mock으로 만든 객체로 교체 함.

모든 @Test 마다 자동으로 리셋된다.

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SampleControllerTest3 {

    @Autowired // test용 RestTemplate
    TestRestTemplate testRestTemplate;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void hello() throws Exception{
        when(mockSampleService.getName()).thenReturn("hoit");

        String result = testRestTemplate.getForObject("/hello", String.class);
        assertThat(result).isEqualTo("hello hoit");
    }
}

SampleController가 쓰는 SampleService를 모킹해서 빈을 교체했다.

ApplicationContext 안에 있는 SampleService 빈을 Mock으로 만든 mockSampleService로 교체한다.
그래서 이 테스트에서 Service는 원본 SampleService가 아닌 mockSampleService를 쓰게 된다.

 

WebTestClient

먼저, spring-boot-starter-webflux 의존성을 추가해야 사용할 수 있다.

@AutoConfigureWebTestClient 도 추가해야한다.

WebTestClient 빈을 만들어 주는 역할인데,  이를 안써도 webflux 의존성을 추가하면 정상작동하긴 한다.

 

기존에 사용하던 RestClient는 synchronous였는데 webClient는 asynchronous 이다.
synchronous: 요청하나 보내고 끝날 때까지 기다린 다음에 그 다음 요청을 보낼 수 있음

asynchronous: 요청을 보내고 기다리는 것이 아니라 응답이 오면 그 때 call back이 옴

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class SampleControllerTest4 {

    @Autowired
    WebTestClient webTestClient;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void hello() throws Exception{
        when(mockSampleService.getName()).thenReturn("hoit");

        webTestClient.get().uri("/hello").exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("hello hoit");
    }
}

하지만 꼭 이것 때문이 아니더라도 api가 편해서 쓴다고 한다.
(+ 추가로, mockMvc이 두번째로 불편하고, RestTemplate이 제일 불편하다고 한다.)

 

슬라이스 테스트

레이어 별로 잘라서 테스트할 수 있다.

통합 테스트인 @SpringBootTest 를 바꿔서 쓴다.

@JsonTest

레퍼런스: #

@WebMvcTest

레퍼런스: #

@Controller 들만 빈으로 등록된다.

WebMvcTest auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, 웹 관련들만.
Regular @Component and @ConfigurationProperties beans are not scanned.

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
class SampleControllerTest6 {

    @Autowired // WebMvcTest는 항상 MockMvc로 test 해야 한다.
    MockMvc mockMvc;

    @MockBean // Service는 등록이 되지 않기 때문에 사용하는 의존성을 채워줘야 한다.
    SampleService mockSampleService;

    @Test
    public void hello() throws Exception{
        when(mockSampleService.getName()).thenReturn("jueun");

        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("hello jueun"));
    }
}

@WebFluxTest

레퍼런스: #

@DataJpaTest 

레퍼런스: #

@Repository 들만 빈으로 등록된다.

이외에도 많이 있다.

OutputCaptureRule

특정 로그 메시지가 출력이 되는지 테스트 코드로 확인하고 싶을 때 확인할 수 있다.

@Rule 어노테이션에 의해 public으로 선언해야한다.

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.system.OutputCaptureRule;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTestUtil {

    @Rule
    public OutputCaptureRule output = new OutputCaptureRule();

    @MockBean // SampleController가 쓰는 SampleService를 모킹해서 빈을 교체했다.
    SampleService mockSampleService;

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception{
        when(mockSampleService.getName()).thenReturn("hoit");

        mockMvc.perform(get("/hello2"))
                .andExpect(content().string("hello hoit"));

        assertThat(output.toString())
                .contains("info level")
                .contains("sout");
    }
}
@RestController
public class SampleController {

    @Autowired
    private  SampleService sampleService;

    Logger logger = LoggerFactory.getLogger(SampleController.class);

    @GetMapping("/hello2")
    public String hello2() {
        logger.info("info level");
        System.out.println("sout");
        return "hello " + sampleService.getName();
    }
}