포스트

REST API(3) Event 생성 API 구현 - Event Controller 테스트 만들기

구현 Package 및 Class

image


EventControllerTests 클래스 생성

우선 테스트 클래스를 생성하면 @RunWith(SpringRunnder.class) 어노테이션을 붙여주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package me.hantomas.restapi.events;

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.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTests {
    @Autowired
    MockMvc mockMvc;
    @Test
    public void createEvent(){
        
    }

}

  • @WebMvcTest : MockMvc bean을 자동 설정 해준다. 웹과 관련된 빈만 등록해 주기 때문에 슬라이싱 테스트라고 한다.
  • MockMvc : 스프링 MVC 테스트의 핵심 클래스이다. Mocking되어 있는 DispatcherServlet을 상대로 가짜 요청을 보내고 그 응답을 확인할 수 있는 테스트를 만들 수 있다. 웹 서버를 띄우지 않고도 스프링 MVC (DispatcherServlet)가 요청을 처리하는 과정을 확인 할 수 있기 때문에 컨트롤러 테스트용으로 자주 쓰인다. 웹 서버를 띄우지 않아서 빠르긴 하지만 DispatcherServlet 까지 만들어야 되기 때문에 단위 테스트보다는 빠르진 않다.

EventControllerTests 구현 (1)

  • 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인 해보자.
    • Location Header에 생성된 이벤트를 조회할 수 있는 URI가 담겨 있는지 확인해 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package me.hantomas.restapi.events;

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.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

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

@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTests {
    @Autowired
    MockMvc mockMvc;

    @Test
    public void createEvent() throws Exception{
        mockMvc.perform(post("/api/events/")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .accept(MediaTypes.HAL_JSON)
                )
                .andDo(print())
                .andExpect(status().isCreated())
        ;
    }
}

  • perform() 안에 HTTP 요청을 보낼 수 있는데, contentType()을 사용해서 요청의 본문에 들어가는 형식accept() 를 사용해서 원하는 응답 형식을 지정할 수 있다.
  • andExpect() 로 응답을 받아올 수 있다. status().isCreated()201 응답을 의미한다.
  • andDo(print())를 통해서 실제 값들이 어떻게 나오는지 확인할 수 있다.

EventController 구현 (1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package me.hantomas.restapi.events;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

import java.net.URI;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@Controller
public class EventController {
        
    public ResponseEntity createEvent(){
        URI createdUri = linkTo(methodOn(EventController.class).createEvent()).slash("{id}").toUri();
        return ResponseEntity.created(createdUri).build();
    }
}

  • @Controller 어노테이션을 추가해 준다.
  • @PostMapping("/api/events") : “/api/events” 경로로의 POST 요청을 처리하는 메서드를 정의 한다는 것을 의미한다.
  • linkTo() 메서드를 통해 link를 만들어 줄 수 있다.
    • methodOn(EventController.class).createEvent())EventController 클래스 안에 메서드 createEvent()에 대한 링크(@RequestMapping("/api/events")을 통해 /api/events"를 요청하는)를 만든다는 의미이다.
    • linkTo(methodOn(EventController.class).createEvent()).slash("{id}").toUri(); 👉 http://localhost/api/events/{id}

EventControllerTests 결과값 (1)

image
image


EventControllerTests 구현 (2)

  • 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인 해보자.
    • Location Header에 생성된 이벤트를 조회할 수 있는 URI가 담겨 있는지 확인해 보자.
    • 임의의 id값(10)을 넣어주고 결과로 나오는지 확인해 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package me.hantomas.restapi.events;

import com.fasterxml.jackson.databind.ObjectMapper;
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.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;

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

@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTests {
    @Autowired
    MockMvc mockMvc;
    @Autowired
    ObjectMapper objectMapper;

    @Test
    public void createEvent() throws Exception{
        Event event = Event.builder()
                .name("Spring")
                .description("REST API Development With Spring")
                .beginEnrollmentDateTime(LocalDateTime.of(2024,02,19,14,21))
                .closeEnrollmentDateTime(LocalDateTime.of(2024,02,20,14,21))
                .beginEventDateTime(LocalDateTime.of(2024,02,21,14,21))
                .endEventDateTime(LocalDateTime.of(2024,02,22,14,21))
                .basePrice(100)
                .maxPrice(200)
                .limitOfEnrollment(100)
                .location("강남역 D2 스타트업 팩토리")
                .build();

        mockMvc.perform(post("/api/events/")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .accept(MediaTypes.HAL_JSON)
                        .content(objectMapper.writeValueAsString(event))
                )
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("id").exists())
        ;
    }
}

  • ObjectMapper : 우리는 요청할 데이터의 contentType을 JSON으로 설정했다. 따라서 값을 JSON으로 줘야하는데, SpringBoot에는 이미 ObjectMappper가 빈으로 등록이 되어있어서,.content(objectMapper.writeValueAsString(event))을 통해 객체를 JSON 문자열로 바꾸어 본문에 넣어 줄 수 있다.

EventController 구현 (2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package me.hantomas.restapi.events;

import org.springframework.hateoas.MediaTypes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import java.net.URI;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;

@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {
    @PostMapping
    public ResponseEntity createEvent(@RequestBody Event event){
        URI createdUri = linkTo(EventController.class).slash("{id}").toUri();
        event.setId(10);
        return ResponseEntity.created(createdUri).body(event);
    }
}

  • @RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE) : RequsetMapping 안에 있는 모든 핸들러들은 HAL_JSON_UTF8_VALUE 타입의 응답을 보내게 된다. @PostMapping의 경로는 지워줘도 된다.
  • @RequestBody Event event : 요청 본문에 있는 데이터를 event에 매핑한다.
  • methodOn() 지정을 풀어준다.
  • 임의의 id(10)을 event.setId(10); 지정해 준다.
  • .body(event); 객체를 body에 담아서 보내준다.

EventControllerTests 결과값 (2)

image
image

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.