안녕하세요, Brad입니다. 오늘은 수업시간에 실습해본 테스트케이스들에서 사용된 여러 코드들을 하나하나 분석해보는 작업을 해보았습니다.
TestRestTemplate
의 역할 및 의미는 뭘까요?
현재 테스트를 작성할 때 AcceptanceTest
클래스를 상속받아 이 안에 있는 속성들을 사용하는데요. 그 중 하나가 AcceptanceTest
의 template 객체를 사용합니다. 이 정체가 뭘까요?
TestRestTemplate
is a convenience alternative to Spring’sRestTemplate
that is useful in integration tests. You can get a vanilla template or one that sends Basic HTTP authentication (with a username and password). In either case, the template behaves in a test-friendly way by not throwing exceptions on server-side errors.출처 : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
Spring 공식 사이트에 따르면 RestTemplate
의 대안으로 통합 테스트 하기에 편하고, 기본적인 HTTP authentication을 받기에 편하다고 설명이 되어있네요. 그리고 서버 쪽의 에러처리에도 용이하다고 말합니다. 음.. 무슨 말인지 아직 안와닿네요. RestTemplate
은 또 뭘까요?
Why should we use RestTemplate:- Basically, RestTemplate is used to make HTTP Rest Calls (REST Client). If we want to make an HTTP Call, we need to create an HttpClient, pass request and form parameters, setup accept headers and perform unmarshalling of response, all by yourself, Spring Rest Templates tries to take the pain away by abstracting all these details from you. It is thread-safe, once created you can use it as a callbacks to customize its works.
출처 : https://www.oodlestechnologies.com/blogs/Why-use-Rest-Template
위 사이트에 의하면 기본적은 RestTemplate
는 HTTP Rest 호출을 만드는데 사용된다고 합니다. 저희가 직접 HTTP 요청을 만들기 위해서는 너무나 해야할 작업들이 많기 때문에 Spring Rest Templates에서 만들어준다고 하네요. 결국 TestRestTemplate
은 테스트코드에서 기존 프로덕션 코드의 RestTemplate
가 하던일을 대신 함으로써 테스트 할 수 있는 환경을 제공한다고 생각할 수 있을 것 같네요.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
로 사용 중일 때는 private TestRestTemplate template;
을 객체 선언없이 @Autowired
로 주입받아 사용할 수 있습니다!
이번 미션의 몇 개의 테스트 케이스 샘플을 보면서 사용법을 익혀봅시다!
- 테스트케이스1
@Test
public void createForm() throws Exception {
ResponseEntity<String> response = template().getForEntity("/users/form", String.class); // get방식으로 요청을 보냄
softly.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // softly를 쓰면 따로 import 필요없음
log.debug("body : {}", response.getBody());
}
위에서 본 TestRestTemplate
객체는 위 코드에서 template()
메서드를 통해 가져오는데요. getForEntity
는 get방식으로 "/users/form"으로 요청을 보내는 것 같네요. 그리고 그것에 대한 요청결과는 ResponseEntity<String>
을 통해 받습니다. 위 코드에서 response.getBody()
를 했을 때는 요청결과값으로 받은 html문서 내용을 그대로 확인할 수 있었습니다!
다음부터 테스트할 '테스트케이스2'와 '테스트케이스3'은 다음 프로덕션 코드를 테스트하기 위함입니다.
@PostMapping("/login")
public String login(String userId, String password, HttpSession session) {
try {
User user = userService.login(userId, password);
session.setAttribute(HttpSessionUtils.USER_SESSION_KEY, user);
return "redirect:/";
} catch (UnAuthenticationException e) {
return "user/login_failed";
}
}
- 테스트케이스2
@Test
public void login() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("userId", "brad903");
params.add("password", "1234");
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(params, headers);
ResponseEntity<String> response = template().postForEntity("/login", request, String.class);
softly.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); // Controller에서 redirect시 302 상태메시지를 보냄
softly.assertThat(response.getHeaders().getLocation().getPath()).isEqualTo("/");
}
위에서 코드 중 getLocation().getPath()
메서드의 정체가 궁금해서 몇가지 테스트를 통해 몇가지를 배울 수 있었습니다. 하나는 서버에서 redirect로 보내면 HttpStatus상태값이 FOUND(302)로 온다는 것입니다. 만약 Forward로 뷰만 전달했다면 OK(200)로 오구요. 그리고 두번째는 redirect로 보낸 것의 response엔 redirect된 uri를 얻을 수 있습니다. header에는 이렇게 담겨져 있습니다.
{Location=[http://localhost:51411/user/login], Content-Language=[en-KR], Content-Length=[0], Date=[Sat, 08 Dec 2018 11:23:45 GMT]}
그럼 다시 '테스트케이스2'를 살펴보겠습니다. postForEntity()
메서드를 사용하기 위해선 우선 reqeust를 만들어줘야 하는데요. reqeust는 HttpEntity<MultiValueMap<String, Object>>
라는 타입을 가지고 있네요. MultiValueMap<String, Object>
는 파라미터을 담는데 사용되었고 객체 생성할 때는 new HttpEntity<MultiValueMap<String, Object>>(params, headers);
그 위에서 만들어준 headers까지 포함하여 만드네요! post방식으로 요청을 보내려면 조금 전 get방식보다는 까다롭네요.
테스트를 살펴보면 우선 response.getStatusCode()
를 통해 HttpStatus값을 확인하고, response.getHeaders().getLocation().getPath()
를 통해 redirect해주는 path를 확인하는 작업을 하였습니다.
- 테스트케이스3
@Test
public void login_failed() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("userId", "javajigi");
params.add("password", "test2");
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(params, headers);
ResponseEntity<String> response = template().postForEntity("/login", request, String.class);
softly.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
softly.assertThat(response.getBody().contains("아이디 또는 비밀번호가 틀립니다")).isTrue();
}
테스트케이스3는 로그인시 userId와 password가 틀렸을 때 보내주는 View를 확인해보기 위한 테스트입니다. 위에서 redirect할때는 redirect할 path를 response를 받을 수 있었는데, forward의 경우 그 path값이 없는 것을 확인했었습니다(생각해보니 redirect와 달리 forward는 path가 바뀌지 않으니 그 값이 안넘어오는 것일수도 있겠네요). 무튼 이 경우 테스트 해볼 수 있는 것은 forward로 받은 뷰를 getBody()
로 html 코드를 받은 다음 그 안에 저희가 원하는 문구나 코드가 있는지입니다. userId나 password가 틀릴 경우 "user/login_failed"로 forward하는데요. 이 안에 "아이디 또는 비밀번호가 틀립니다"라는 문구가 있거든요. 만약 이 문구가 있다면 저희가 forward를 잘했는지 확인할 수 있겠죠!!
참고) Intellij에서 navigation과 code작성창 왔다갔다 할 때 단축키
맥의 경우 'command
+ 1'을 누르면 navigation창으로 포커스가 됩니다. 그리고 esc
를 누르면 다시 코드를 작성하는 창으로 포커스가 옮겨집니다. 윈도우의 경우에도 navigation창을 활성화하는 단축키와 esc
로 똑같이 활용할 수 있을 것 같네요.
테스트코드 내 코드들이 아직은 너무 낮설지만 계속 써가면서 익숙해져야할 것 같네요!
오늘 추가적으로 공부한 내용
'TIL' 카테고리의 다른 글
Today's Dev Notes(2018-12-10) (0) | 2018.12.10 |
---|---|
Today's Dev Notes(2018-12-09) (0) | 2018.12.09 |
Today's Dev Notes(2018-12-06) (0) | 2018.12.06 |
Today's Dev Notes(2018-12-03) (0) | 2018.12.03 |
Today's Dev Notes(2018-12-02) (0) | 2018.12.02 |