레알윙 2020. 4. 16. 23:43
반응형

테스트란

테스트 파일에는 @SpringBootTest가 아래와같이 붙어있다. @SpringBootTest 어노테이션은 @SpringBootApplication 어노테이션이 붙어있는 스프링 메인 애플리케이션을 찾아간다. 이후 메인부터 시작하는 모든 Bean을 찾는다. 다음으로는 테스트용 애플리케이션 context를 만들면서 Bean을 등록해준다. 이 중에 MockBean에 해당되는 Bean을 찾아서 교체를 해준다. 교체된 MockBean은 테스트마다 리셋이된다.

 

 

테스트 예시 코드

@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 "JinSeok";
	}
}

 

위와같이 테스트를 하기위해서는 pom.xml에 아래와 같이 의존성이 정의되어야한다.

 

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

 

 

Mock 타입 테스트

Mock타입은 서블릿 컨테이너(톰캣 같은 것)를 테스트용으로 띄우지 않고, Mockup을 해서 서블릿을 Mocking 한 것을 띄워준다. dispatcherServlet이 만들어지긴 하는데 Mockup이 되어, dispatcherServlet에 요청을 보내는 것 처럼 실험을 할 수 있다. 이 때 mockup 된 서블릿과 상호작용을 하려면 MockMVC라는 클라이언트를 사용해야 한다.

MockMVC 라는 클라이언트를 사용하려면 @AutoConfigureMockMvc 어노테이션을 붙여주고, 주입 사용하면 된다.

Mock 객체는 내장 톰캣이 구동이 안된다.

 

코드설명

  • mockMvc.perform(get("/hello"))
    • get요청을 보내는 것
  • andExpect()
    • ~하길 기대하는 것
  • andDO();
    • ~을 실행
@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())
		.andExpect((ResultMatcher) content().string("hello JinSeok"))
		.andDo(print());
	}
}

 

결과

 

 

RANDOM-PORT / DEFINED-PORT 타입

실제로 서블릿 컨테이너(was 종류 - 내장 톰캣)이 뜬다. 테스트용 rest template나, 테스트용 웹 클라이언트를 사용해야 된다.

 

RANDOMRANDOM_PORT는 랜덤한 포트를 배정받고, DEFINED_PORT는 포트를 정해줄 수 있다.

 

 

TestRestTemplate

TestRestTemplate을 주입받아서 사용을 한다.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
	
	@Autowired
	TestRestTemplate testRestTemplate;
	
	@Test
	public void hello() throws Exception {
		String result = testRestTemplate.getForObject("/hello", String.class);
		assertThat(result).isEqualTo("hello Jinseok");
	}
}

 

@MockBean

SampleService까지 말고 SampleController까지만 가게 하고 싶을 때는 @MockBean을 사용하면 된다.  이 어노테이션을 사용하여 Service를 만들어 컨트롤 할 수 있다.

 

application context안에 들어있는 SampleService Bean@MockBean으로 교체해주기 때문에 mockSampleService를 사용하게 된다.

 

SampleService에서 getNameJinseok을 반환하지만 mockSampleServicejs라는 문자열을 리턴한다. 

 

이 테스트에서는 mockSampleService를 사용하기 때문에 junit5 테스트를 하게 된다면 hello JinSeok이 아닌 hello js가 나오게 된다.

 

SampleController가 사용하는 SampleServicemocking해서 BeanmockSampleService로 교체를 하였다

 

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {
	
	@Autowired
	TestRestTemplate testRestTemplate;
	
	@MockBean
	SampleService mockSampleService;
	
	@Test
	public void hello() throws Exception {
		when(mockSampleService.getName()).thenReturn("js");
		String result = testRestTemplate.getForObject("/hello", String.class);
		assertThat(result).isEqualTo("hello js");
	}
}

 

WebTestClient

WebClient는 Rest Client중에 하나이다. 

 

기존에 사용하는 Rest Client는 synchronous(동기)이다. 하지만 WebClient는 asynchronous(비동기)로 되어있다. 요청을 보내고, 기다리지 않고, 응답이 오면 call back이 온다. 이때 call back을 실행을 할 수 있다.

 

WebTestClient는 테스트할 때 WebClient와 비슷한 API를 사용할 수 있다.

 

WebClient를 사용할라면 아래와같이 의존성이 추가되어야 한다.

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

 

status와 body를 체크해 준다.

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

	@Autowired
	WebTestClient webTestClient;
	
	@MockBean
	SampleService mockSampleService;
	
	@Test
	public void hello() throws Exception {
		when(mockSampleService.getName()).thenReturn("js");
		
		webTestClient.get().uri("/hello").exchange().expectStatus().isOk()
				.expectStatus().isOk()
				.expectBody(String.class).isEqualTo("hello JinSeok");
	}
}

 

 

 

슬라이스 테스트

@SpringBootTest 이 어노테이션이 @SpringBootApplication을 찾아간다. 여기서부터 시작하는 Bean을 다 찾고, SpringBootTest에 저장을 한다. Bean들을 찾아 Mock Bean과 교체해 준다. 즉 모든 Bean을 교체 및 사용한다.

 

위와 다르게 테스트할 Bean만 등록하고 싶을 때 사용하는 것이 슬라이스 테스트이다.

 

웹 레이어(@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver)만 Bean으로 등록해준다는 특징이 있다. @Service, @Repository 같은 Bean들은 Bean으로 등록해주지 않는다.

 

그렇기 때문에 테스트 시 의존성이 깨지기 때문에 @MockBean으로 받아서 사용한다.

 

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

	@Autowired
	MockMvc mocMvc;

	@MockBean
	SampleService mockSampleService;

	@Test
	public void hello() throws Exception {
		when(mockSampleService.getName()).thenReturn("js");
		mocMvc.perform(get("/hello"))
				.andExpect(content().string("hello js"));
	}
}

 

위와같이 사용하면 SampleController만 Bean으로 등록되어 가벼운 테스트가 된다.

 

@WebMvcTest 말고도, @JsonTest, @WebFluxTest, @DataJpaTest 등도 슬라이스 테스트 방법이다.

 

 

 

OutputCapture

junit의 Rule을 확장해서 만든 것이다.

 

로그를 비롯한 console에 찍히는 모든 것을 캡쳐 한다.

 

@RestController
public class SampleController {
	Logger logger = LoggerFactory.getLogger(SampleController.class);
	@Autowired
	private SampleService sampleService;
	
	@GetMapping("/hello")
	public String hello() {
		logger.info("여기는 컨트롤러");
		System.out.println("skip");
		return "hello " + sampleService.getName();
	}
}

 

테스트 클래스에서 OutputCapture Rule을 선언해서(꼭 public으로 해야한다.) 테스트 해볼 수 있다.

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

	@Rule
	public OutputCaptureRule outputcapture = new OutputCaptureRule();
	
	@Autowired
	MockMvc mocMvc;

	@MockBean
	SampleService mockSampleService;

	@Test
	public void hello() throws Exception {
		when(mockSampleService.getName()).thenReturn("js");
		mocMvc.perform(get("/hello"))
				.andExpect(content().string("hello js"));
		
		assertThat(outputcapture.toString())
			.contains("여기는 컨트롤러")
			.contains("skip");
	 
	}
}
반응형