Test 1) JSON ์๋ต์ผ๋ก 201์ด ๋์ค๋์ง ํ์ธ.
- Location ํค๋์ ์์ฑ๋ ์ด๋ฒคํธ๋ฅผ ์กฐํํ ์ ์๋ URI ๋ด๊ฒจ ์๋์ง ํ์ธ.
- id๋ DB์ ๋ค์ด๊ฐ ๋ ์๋ ์์ฑ๋ ๊ฐ์ผ๋ก ๋์ค๋์ง ํ์ธ.
์ผ๋จ Test ํด๋์ค๋ฅผ ๋ง๋ ๋ค
- ํ
์คํธ๋ฅผ ํ ํด๋์ค์์ ๋งฅ ๊ธฐ์ค์ผ๋ก ๋จ์ถํค
cmd + shift + T
๋ฅผ ๋๋ฌ์ฃผ๋ฉด ํ ์คํธ ํด๋์ค ์์ฑํด์ค๋ค.
Location URI๋ฅผ ๋ง๋ค๊ฑด๋ฐ, ์ฌ๊ธฐ์๋ HATEOAS
๊ฐ ์ ๊ณตํ๋ linkTo()
, methodOn()
์ ์ฌ์ฉํ ๊ฒ ์ด๋ค.
๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ๋ณํ ํ ๋, ObjectMapper
๋ฅผ ์ฌ์ฉ.
EventController ๊ตฌํ
package me.iseunghan.demoinflearnrestapi.events;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
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.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
public class EventController {
private final EventRepository eventRepository;
public EventController(EventRepository eventRepository) {
this.eventRepository = eventRepository;
}
@PostMapping
public ResponseEntity createEvent(@RequestBody Event event) {
Event newEvent = this.eventRepository.save(event);
//link๋ฅผ ์์ฑํ ๋,HATEOAS๊ฐ ์ ๊ณตํ๋ linkTo(), methodOn()์ ์ฌ์ฉ
//methodOn()์ ์ฌ์ฉํ์ง ์์ ์ด์ : URL์ด ํด๋์ค ๋ ๋ฒจ์ ๋ถ์๊ธฐ ๋๋ฌธ์ ๋ฉ์๋๋ฅผ ํธ์ถ ์ํด๋ ๋๋ค.
URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createUri).body(event); //201์๋ต์ Uri์ ๋ด์์ ๋ฆฌํด์ํจ๋ค.
}
}
์์์ methodOn()
์ ์์ฐ๊ณ linkTo(T class)
๋ก ๋ฐ๋ก ๋ค์ด๊ฐ ์ด์ ๋, ํด๋์ค ๋ ๋ฒจ์ ๋ถ์ @RequestMapping
๋๋ฌธ์ด๋ค.
Link๋ฅผ ๋ง๋ค๋์๋ @PostMapping("/api/events")
์ฒ๋ผ ๋ฉ์๋๋ ๋ฒจ์ ๋ถ์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ฒ๋ผ ์ด๋ฃจ์ด์ง๋๋ฐ,
@Controller
public class Controller {
...
@PostMapping("/api/events")
public ResponseEntity createEvent(@RequestBody Event event){
URI createUri = linkTo(methodOn(EventController.class).createEvent(null)).slash(newEvent.getId()).toUri();
...
}
๋ง์ฝ ์์ ๊ฐ์ด URL์ด ๋ฉ์๋ ๋ ๋ฒจ์ ๋ถ์๋ค๋ฉด methodOn()
๊น์ง ์ฌ์ฉํด์ผ ํ๋ค.
EventControllerTest ํด๋์ค ๊ตฌํ
package me.iseunghan.demoinflearnrestapi.events;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
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;
/**
* MockMvc
* - ์คํ๋ง MVC ํ
์คํธ ํต์ฌ ํด๋์ค
* - ์น์๋ฒ๋ฅผ ๋์ฐ์ง ์๊ณ ๋ ์คํ๋ง Mvc(DispatherServlet)๊ฐ
* ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์ ํ์ธํ ์ ์๊ธฐ ๋๋ฌธ์ ์ปจํธ๋กค๋ฌ ํ
์คํธ์ฉ์ผ๋ก ์์ฃผ ์ฐ์.
*/
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
//MockBean์ผ๋ก ๋ฑ๋กํ๋ ์ด์ : @WebMvcTest๋ ์น ๊ด๋ จ ๋น๋ค๋ง ๋ฑ๋กํด์ฃผ๊ธฐ ๋๋ฌธ์, ์ง์ ๋ฑ๋กํด์ผํ๋ค.
//(์ฃผ์) ๊ธฐ์กด ๋น์ ํ
์คํธ์ฉ ๋น์ด ๋์ฒด ํ๋ค.
@MockBean
EventRepository eventRepository;
@Test
public void createEvent() throws Exception {
Event event = Event.builder()
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.closeEnrollmentDateTime(LocalDateTime.of(2020,9,8,2,45))
.beginEventDateTime(LocalDateTime.of(2020,9,9,2,45))
.endEventDateTime(LocalDateTime.of(2020,9,10,2,45))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("๊ฐ๋จ์ญ 1๋ฒ ์ถ๊ตฌ")
.build();
event.setId(10);
Mockito.when(eventRepository.save(event)).thenReturn(event);
mockMvc.perform(post("/api/events/")
.contentType(MediaType.APPLICATION_JSON)//๋ณธ๋ฌธ ์์ฒญ์ json์ ๋ด์์ ๋ณด๋ด๊ณ ์๋ค๊ณ ์๋ ค์ค.
.accept(MediaTypes.HAL_JSON)//HAL_JSON์ผ๋ก ๋ฐ๋๋ค.
.content(objectMapper.writeValueAsString(event)))//์์ฒญ ๋ณธ๋ฌธ์ json์ผ๋ก ๋ณํํ ๋ฃ์ด์ค๋ค
.andDo(print())//์ด๋ค ์๋ต๊ณผ ์์ฒญ์ ๋ฐ์๋์ง ํ์ธ๊ฐ๋ฅ.
.andExpect(status().isCreated())//201์์ฒญ์ด ๋ค์ด์๋์ง?
.andExpect(jsonPath("id").exists()) //json์ id๊ฐ ์๋์ง?
.andExpect(header().exists(HttpHeaders.LOCATION)) //header์ Location์ ๋ณด๊ฐ ๋ด๊ฒจ์๋์ง
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE))//content-type์ "application/hal+json"๊ฐ ๋์ค๋์ง?
;
}
}
mockMvc.perform(post("/api/events/")
post๋ฐฉ์์ผ๋ก "api/events/" URL๋ฅผ ๋๊ฒจ์ ํธ์ถํ๋ค..contentType(MediaType.APPLICATION_JSON)
๋ณธ๋ฌธ ์์ฒญ์ JSON์ ๋ด์์ ๋ณด๋ด๊ณ ์๋ค๊ณ ๋ช
์.accept(MediaType.HAL_JSON)
HAL + JSON ํ์
์ผ๋ก ๋ฐ๊ฒ ๋ค๋ ์๋ฏธ.content(objectMapper.writeValueAsString(event))
์์ฒญ ๋ณธ๋ฌธ์ event ๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ๋ณํํด์ ๋ฃ์ด์ค๋ค..andDo(print())
์๋ต, ์์ฒญ print.andExpect(status().isCreate())
201์์ฒญ์ด ๋ค์ด์๋๊ฐ.andExpect(jsonPath("id").exists())
json์ id๊ฐ ์กด์ฌํ๋๊ฐ?
@MockBean, Mokito.when().then()
Test๋์ค Spring์์ ์ด๋ ์์กด์ฑ๋ ํ์ํ์ง ์๋ค๋ฉด, Mockito์ @Mock์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์ข์ต๋๋ค. ์๋ํ๋ฉด, ์ฐ๋ฆฌ๊ฐ ๋ฐ๋ผ๋ ๋น ๋ฅด๊ณ ์์กด์ฑ์๋ unit test๋ก ๊ฐ๋ ๋ฐฉํฅ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. ํ์ง๋ง, test ๋์ค spring container๊ฐ ๊ด๋ฆฌํ๋ bean๋ค ์ค ํ๋๋ผ๋ ์ถ๊ฐํ๊ฑฐ๋ Mockingํ๊ณ ์ถ๋ค๋ฉด @MockBean์ ์ ํํ ์ ์์ต๋๋ค. @MockBean์ ApplicationContext์ mock๊ฐ์ฒด๋ฅผ ์ถ๊ฐํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ mock๊ฐ์ฒด๋ ๊ฐ์ type์ ์ด๋ฏธ ์กด์ฌํ๋ Bean๋ค์ ๋์ ํ ๊ฒ์ ๋๋ค. ํ์ง๋ง, @MockBean์ผ๋ก ๋์ ๋ context๋ ๋ค๋ฅธ context์ด๋ฏ๋ก Spring Boot๋ ApplicationContext๋ฅผ test ์ด๊ธฐํ ์ค์ ๋ค์ ๋ก๋ฉํด์ผํฉ๋๋ค.
ํ ์คํธ ์คํจ
ํ
์คํธ ์ฝ๋์์๋ eventRepoitory๋ฅผ Mock ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋๋ฐ, @MockBean์ ์ด์ฉํด์ ์์ฑํ ๊ฐ์ฒด์ ๋ฉ์๋๊ฐ ๋ฆฌํด ํ์
์ด ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ null์ ๋ฆฌํดํ๊ณ , ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์
์ธ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ๊ฐ์ ๋ฆฌํดํ๋ค. ๊ทธ๋ฆฌํ์ฌ, ํ
์คํธ์์ Controller์ UrI๋ง๋๋ ๊ณผ์ ์์
newEvent.getId() ๋ฅผ ํธ์ถํ๋ ๊ณผ์ ์์ newEvent๊ฐ null์ด๊ธฐ ๋๋ฌธ์ NullpointerException์ด ๋ฐ์ํ์ฌ ํ
์คํธ๊ฐ ๊นจ์ง๊ฒ ๋๋ค.
์ด๋ด๋, Mockito๋ฅผ ์ด์ฉํ๋ค..
Mockito๋ Mock ๊ฐ์ฒด์ ๋ฉ์๋๊ฐ ์๋ง์ ๊ฐ์ ๋ฆฌํดํ๋ ์คํ
์ ๋ง๋ค ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ ์๋ค. ์ด ๋ฉ์๋๋ when - then์ ํ์์ ๋๊ณ
๊ทธ๋ฆฌํ์ฌ, when
: eventRepository.save ๋ฉ์๋๊ฐ ํธ์ถ ๋ ๋! -> thenReturn
: event ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
์ถ์ฒ : https://javacan.tistory.com/entry/MocktestUsingMockito