부트 개념과 활용-13.테스트/테스트 유틸
테스트
시작은 일단 spring-boot-starter-test 의존성 추가
@SpringBootTest
- @RunWith(SpringRunner.class)랑 같이 써야 함. (@ExtendWith(SpringExtension.class) // junit 5 기준)
- 빈 설정 파일은 설정을 안해주나? 알아서 찾습니다. (@SpringBootApplication)
- webEnvironment
- MOCK: mock servlet environment. 내장 톰캣 구동 안 함.
- RANDON_PORT, DEFINED_PORT: 내장 톰캣 사용 함.
- NONE: 서블릿 환경 제공 안 함.
@MockBean
- ApplicationContext에 들어있는 빈을 Mock으로 만든 객체로 교체 함.
- 모든 @Test 마다 자동으로 리셋.
예제 코드
@RestController
public class SampleController {
@Autowired
private SampleService sampleService;
@GetMapping("hello")
public String hello(){
return "hello " + sampleService.getName();
}
}
@Service
public class SampleService {
public String getName() {
return "jaeuk";
}
}
@ExtendWith(SpringExtension.class) // junit 5 기준
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
// 서블릿 컨테이너를 테스트용으로 띄우지 않고, 서블릿을 모킹한게 뜬다.
// 디스패처 서블릿이 뜨긴 하는데, 디스패처 서블릿으로 요청을 보내는것과 비슷하게 실행은 할 수 있는데, 목업이 된 서블릿이 인터렉션 하려면 MockMvc 클라이언트를 사용해야 한다.
@AutoConfigureMockMvc // MockMvc를 만드는 가장 쉬운 방법
class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception{
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello jaeuk"))
.andDo(print());
}
}
랜덤포트 사용할때 서블릿 컨테이너가(내장 톰켓이) 뜬다.
이땐 MockMvc가 이 아니라 TestRestTemplate이나 WebTestClient를 써야 한다.
TestRestTemplate 사용
@ExtendWith(SpringExtension.class) // junit 5 기준
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 랜덤포트 사용할때 서블릿 컨테이너가(내장 톰켓이) 뜬다.
// 이땐 MockMvc가 이 아니라 TestRestTemplate이나 WebTestClient를 써야 한다.
class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate; // 이상태에선 주입 가능
// 근데 이 컨트롤러가 서비스까지 연결되어 있다.(테스트 커짐)
@MockBean
SampleService mocSampleService; // 목빈으로 설정하면 sampleService 를 mockSampleService 만들어서 쓰는 것
@Test
public void hello() throws Exception{
when(mocSampleService.getName()).thenReturn("jaeuk"); // getNaem 이 호출되면 jaeuk 리턴받는다
// SampleService 얘를 모킹해서 @MockBean 을 사용해 그 빈을 교체한 것.
String result = testRestTemplate.getForObject("/hello", String.class);
assertEquals(result, "hello jaeuk");
}
}
WebTestClient 사용
@ExtendWith(SpringExtension.class) // junit 5 기준
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 랜덤포트 사용할때 서블릿 컨테이너가(내장 톰켓이) 뜬다.
// 이땐 MockMvc가 이 아니라 TestRestTemplate이나 WebTestClient를 써야 한다.
class SampleControllerTest {
// (스프링 5에서 새로 추가) 스프링 웹 플럭스 쪽에 추가된 레스트 클라이언트 중에 하나.
// 기존 사용하던 레스트 클라이언트는 Synchronous(동기) - 요청 하나 보내고 기존에 하나 끝나야 다시 하나 보낼 수 있다.
// asynchronous(비동기) 하다. 요청을 보내고 기다리는게 아니라, 응답이 오면 그때 바로 콜백이 온다.
// 그때 이벤트가 오기 때문에 콜백을 바로 실행할 수 있다.
// 테스트 코드에서도 웹 클라이언트랑 동일한 api를 사용 가능하다.
@Autowired
WebTestClient webTestClient; // webflux 의존성 추가해줘야 함.
// 근데 이 컨트롤러가 서비스까지 연결되어 있다.(테스트 커짐)
@MockBean
SampleService mocSampleService; // 목빈으로 설정하면 sampleService 를 mockSampleService 만들어서 쓰는 것
@Test
public void hello() throws Exception{
when(mocSampleService.getName()).thenReturn("jaeuk"); // getNaem 이 호출되면 jaeuk 리턴받는다
// SampleService 얘를 모킹해서 @MockBean 을 사용해 그 빈을 교체한 것.
webTestClient.get().uri("/hello").exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello jaeuk");
}
}
통합 테스트
@SpringBootTest 얘가 @SpringBootApplication를 찾아서
거기서부터 시작하는 모든 빈을 스캔하고,
모든 걸 테스트용 애플리케이션 컨텍스트를 만들면서 모든 빈을 다 등록 후,
목으로 된 빈만 찾아서 목 빈으로 교체해준다.
목 빈은 테스트마다 리셋되니까 처음부터 모킹을 하면서 시작해도 지장이 없다.
수많은 빈들이 등록되는 건 싫고,
그럼 난 내가 테스트하고 싶은 것만 등록하고 싶다면.
슬라이스 테스트용 애노테이션
슬라이스 테스트
- 레이어 별로 잘라서 테스트하고 싶을 때
- @JsonTest (json 타입 테스트)
- @WebMvcTest (내가 하고싶은 컨트롤러 하나만 등록 가능)
하나만 사용하니까 더 빠름
@ExtendWith(SpringExtension.class) // junit 5 기준
@WebMvcTest(SampleController.class) // 웹과 관련된 부분만 빈으로 등록이 된다.
// 일반적인 컴포넌트는 빈으로 등록 안됨(서비스, 레파지토리 등 x) 사용하는 의존성이 있다면 목 빈으로 채워줘야 함
class SampleControllerTest {
@MockBean
SampleService mocSampleService; // 목빈으로 설정하면 sampleService 를 mockSampleService 만들어서 쓰는 것
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception{
when(mocSampleService.getName()).thenReturn("jaeuk"); // getNaem 이 호출되면 jaeuk 리턴받는다
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello jaeuk"))
.andDo(print());
}
}
- @WebFluxTest
- @WebMvcTest
웹과 관련된 부분만 빈으로 등록이 된다.
- @DataJpaTest
레파지토리만 빈 등록이 됨
테스트 유틸
OutputCapture
@RestController
public class SampleController {
Logger logger = LoggerFactory.getLogger(SampleController.class);
@Autowired
private SampleService sampleService;
@GetMapping("hello")
public String hello(){
logger.info("ju.choi");
System.out.println("skip"); // 이것도 테스트가 된다.(캡쳐된다)
return "hello " + sampleService.getName();
}
}
@ExtendWith({SpringExtension.class, OutputCaptureExtension.class}) // junit 5 기준 (OutputCaptureExtension )
@WebMvcTest(SampleController.class)
class SampleControllerTest {
// @Rule // junit 5에서 rule기반 어노테이션 변경됨.
// public OutputCaptureRule outputCaptureRule = new OutputCaptureRule();
// 로그를 비롯, 콘솔에 찍히는 모든것을 다 캡쳐한다
@MockBean
SampleService mocSampleService; // 목빈으로 설정하면 sampleService 를 mockSampleService 만들어서 쓰는 것
@Autowired
MockMvc mockMvc;
@Test
public void hello(CapturedOutput capturedOutput) throws Exception{
when(mocSampleService.getName()).thenReturn("jaeuk"); // getNaem 이 호출되면 jaeuk 리턴받는다
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello jaeuk"))
.andDo(print());
System.out.println(capturedOutput.toString().contains("ju.choi"));
System.out.println(capturedOutput.toString().contains("skip"));
}
}