반응형
2. @WebMvcTest
DataJpa 어노테이션이 Repository (Dao) 에 대한 테스트라면 WebMvcTest는 Controller을 위한 테스트 어노테이션이다.
Scan 대상은 아래와 같다.
- @Controller
- @ControllerAdvice
- @JsonComponent
- Filter
- WebMvcConfigurer and HandlerMethodArgumentResolver
MockMvc를 자동으로 지원하고 있어 별도의 HTTP 서버 없이 Controller 테스트를 진행할 수 있다.
사용법 역시 간단하다.
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class WebMvcTest {
@Autowired
private MockMvc mvc;
@Test
public void test_샘플() throws Exception {
this.mvc.perform(get("/hello").accept(MediaType.TEXT_PLAIN)) // /hello 라는 url로 text/plain 타입을 요청
.andExpect(status().isOk()) // 위 요청에 따라 결과가 status는 200이며
.andExpect(content().string("Hello World")); // response body에 "Hello World" 가 있는지 검증
}
}
2.1 상황1
- 호출한 URL의 View를 검증한다.
- 시작페이지 구성을 위해 "/" 를 요청하면 home.ftl을 전달하는지 테스트한다.
- code
@Test
public void test_View검증() throws Exception {
this.mvc.perform(get("/")) // /로 url 호출
.andExpect(status().isOk()) // 위 요청에 따라 결과가 status는 200이며
.andExpect(view().name("home")); // 호출한 view의 이름이 home인지 확인 (확장자는 생략)
}
2.2 상황2
- 호출한 URL의 View와 Model 데이터를 검증한다.
- 아직 Service Layer의 상세스펙은 나온 상태가 아니다.
- 시작페이지 구성을 위해 "/" 를 요청하면 home.ftl과 Job, Tect, Essay List를 전달하는지 테스트한다.
- code
// service의 상세스펙이 나오지 않았으니 인터페이스로 service를 먼저 선언한다.
public interface PostService {
List<Job> getJobList();
List<Tech> getTechList();
List<Essay> getEssayList();
}
// 테스트 코드
@MockBean // postService에 가짜 Bean을 등록
private PostService postService;
@Test
public void test_Model검증및ServiceMocking() throws Exception {
Job[] jobs = {new Job("잡플래닛", LocalDateTime.now(), new ArrayList<>())};
Tech[] techs = {new Tech("OKKY", LocalDateTime.now(), new ArrayList<>())};
given(this.postService.getJobList()) // this.postService.getJobList 메소드를 실행하면
.willReturn(Arrays.asList(jobs)); // Arrays.asList(jobs) 를 리턴해줘라.
given(this.postService.getTechList())
.willReturn(Arrays.asList(techs));
given(this.postService.getEssayList())
.willReturn(new ArrayList<>());
mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("home"))
.andExpect(model().attributeExists("jobs")) // model에 "jobs" 라는 key가 존재하는지 확인
.andExpect(model().attribute("jobs", IsCollectionWithSize.hasSize(1))) // jobs model의 size가 1인지 확인
.andExpect(model().attribute("techs", contains(techs[0]))) // techs model이 "OKKY" 라는 객체를 가지고 있는지 확인
.andExpect(model().attribute("essays", is(empty()))); // 빈 Collection인지 확인
}
2.3 상황3
- Job 데이터를 입력받고 "/" 로 redirect 시킨다.
- 아직 Service Layer의 상세스펙은 나온 상태가 아니므로 reqeust, response 결과가 정상적인지만 확인한다.
- Job 스펙에 맞춰 content가 parameter에 포함된다.
- code
// controller 코드
@RequestMapping(value = "/job", method = RequestMethod.POST)
public String addJob(Job job) {
postService.addJob(job);
return "redirect:/";
}
// test 코드
@Test
public void test_Job데이터입력하기() throws Exception {
mvc.perform(post("/job")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("content", "많이 와주세요! http://jojoldu.tistory.com"))
.andExpect(status().is3xxRedirection()) // 302 redirection이 발생했는지 확인
.andExpect(header().string("Location", "/")) // location이 "/" 인지 확인
.andDo(MockMvcResultHandlers.print()); // test 응답 결과에 대한 모든 내용 출력
}
- .param() 외에도 .header(), cookie(), sessionAttr() 등의 메소드들이 지원되니 꼭 사용해보길 바란다!
2.4 상황4
- @ResponseBody를 통해 Json 데이터를 리턴시킨다.
- Ajax 요청에 대응하기 위해 getJob 메소드는 Job 객체의 Json 형태를 리턴시킨다.
- 정상적으로 저장되었는지 확인할 수 있어야 한다.
- code
// Controller 코드
@RequestMapping(value="/job/{idx}")
@ResponseBody
public Job getJob(@PathVariable long idx) {
return this.postService.getJob(idx);
}
// test 코드
@Test
public void test_Json결과비교하기() throws Exception {
Job job = new Job("많이 와주세요! http://jojoldu.tistory.com", LocalDateTime.now(), new ArrayList<>());
given(this.postService.getJob(1)) // getJob 메소드에 인자값 1이 입력될 경우
.willReturn(job); // job 객체를 리턴한다. 이럴경우 service 구현체가 없어도 테스트가 가능하다.
mvc.perform(get("/job/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value("많이 와주세요! http://jojoldu.tistory.com")) // json 데이터중 content 속성의 값을 비교한다.
.andDo(MockMvcResultHandlers.print());
}
- 일반적으로 Json 데이터를 테스트 코드상에서 비교하려면 Json 전체 데이터가 필요한데, update date같이 날짜가 있는 경우엔 전체 데이터 비교가 힘들다. 때문에 Json 데이터 중 일부의 데이터만 비교하는것이 좋다.
- $.content 코드는 일반적으로 jsonPath 라는 문법으로 불린다. jsonPath를 사용하여 content 값만 비교하였다.
- 자세한 사용법은 링크를 참고한다.
2.5 상황5
- Job 데이터 조회시 데이터가 없을 경우 NotFound Exception을 발생시킨다.
- NotFoundExcption은 Job/Tech/Essay 만을 나타낼수 있도록, PostNotFoundException 이라는 새로운 Exception으로 처리한다.
- code
// PostNotFound Exception
@ResponseStatus(HttpStatus.NOT_FOUND) // 404 NOT_FOUND status
public class PostNotFoundException extends RuntimeException{ // 직접 생성한 Exception
public PostNotFoundException(long idx) {
super("could not find post '" + idx + "'.");
}
}
// Controller의 메소드
@RequestMapping(value="/job/{idx}")
public Job getJob(@PathVariable long idx) {
return Optional.ofNullable(this.postService.getJob(idx))
.orElseThrow(() -> new PostNotFoundException(idx)); // this.postService.getJob(idx)가 null일 경우 PostNotFoundException 발생
}
//테스트 코드
@Test
public void test_Exception체크() throws Exception {
given(this.postService.getJob(1)) // getJob 메소드에 인자값 1이 입력될 경우
.willReturn(null); // exception 발생을 위해 null 리턴
mvc.perform(get("/job/1")) // /job/1 을 호출할 경우
.andExpect(status().isNotFound()); // not found exception이 나오는지 아닌지 테스트
}
Controller와 Repository (Dao)의 테스트가 끝이 났다. 위 방삭을 이용하면 기본적인 테스트 코드는 작성가능하다고 생각한다.
반응형