[Spring-REST-API] 1. 프로젝트 생성 및 이벤트 생성 API

의존성

  • Lombok
  • Web
  • HATEOAS
  • REST Docs
  • JPA
  • H2
  • PostgresQL

tip

  • h2와 PostgresQL 둘다 스카프나 별도 설정없이 추가시 h2 인메모리 먼저 사용됨

도메인 구현 및 유닛 테스트

도메인 테스트 코드
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
public class EventTest {

//builder pattern 적용?
@Test
public void builderTest(){
Event event = Event.builder()
.name("name test")
.description("test event")
.build();
assertThat(event).isNotNull();
}

//javabean spec 준수 여부:
//default 생성자로도 생성 가능해야 한다
//setter,getter 사용 가능해야하고
@Test
public void javaBeanTest(){

//given
String testName = "GeunChang";
String testDesc = "Owner";

//when
Event event = new Event(); // default 생성자 테스트
event.setName("GeunChang");
event.setDescription("Owner");

//then
assertThat(event.getName()).isEqualTo(testName);
assertThat(event.getDescription()).isEqualTo(testDesc);
}
}
도메인 모델
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
public class Event {

private Integer id;
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location; //(optional), 이게 없으면 온라인 모임
private int basePrice; //(optional)
private int maxPrice; //(optional)
private int limitOfEnrollment;
private boolean offline;
private boolean free;
private EventStatus eventStatus; //enum
}

//별도 EventStatus.java만드는 게 좋을듯
public enum EventStatus {
DRAFT, PUBLISHED, BEGAN_ENROLLMENT, CLOSED,ENROLLMENT, STARTED, ENDED
}
  • 설명
    • 빌더 패턴 적용여부 테스트 : @Builder로 해결
    • javaBean 스펙 준수 테스트
      • 스펙
        • 기본 생성자로 인스턴스 생성 가능해야 한다
        • getter, setter 사용 가능해야한다
      • Builder 추가시 default 생성자가 추가가 안된다
        –> @NoArgsConstructor로 해결
      • Builder 추가시 모든 아규먼트의 생성자가 public이 아니다(deafult임)
        –>다른 곳에서 해당 클래스 인스턴스 생성이 애매해진다
        –> @AllArgsConstructor로 해결
      • getter,setter -> @Getter , @Setter로 해결
    • @EqualsAndHashCode를 id로만 하는 까닭?
      • 엔티티 필드에 다른 엔티티가 들어가는 등의 상호 참조시 stack 에러 발생 가능
    • @Data를 쓰지 않는 이유
      • EqualsAndHashCode를 모든 프로퍼티를 다 사용 하기 때문
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
```

#도메인 비지니스 및 테스트
```java 도메인 비지니스 테스트 코드
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {

@Autowired
MockMvc mockMvc;

@Autowired
ObjectMapper objectMapper;

@Test
public void createEvent() throws Exception {

Event event = new Event().builder()
.name("springname")
.description("spring rest api")
.beginEnrollmentDateTime(LocalDateTime.of(2019,3,20,11,53))
.closeEnrollmentDateTime(LocalDateTime.of(2019,3,21,11,55))
.beginEventDateTime(LocalDateTime.of(2019,3,22,11,53))
.endEventDateTime(LocalDateTime.of(2019,3,23,11,53))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남")
.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())
;
}
}
  • 설명
    • 특정 객체를 만들고 요청을 주면
      그에 따른 응답이 201 create + json body의 id까지 확인하는 테스트
도메인 비지니스
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class EventController {

@PostMapping("/api/events")
public ResponseEntity createEvent(@RequestBody Event event) {
URI uri = linkTo(methodOn(EventController.class).createEvent(null))
.slash("{id}").toUri();
event.setId(10);
return ResponseEntity.created(uri)
.body(event);
}
}
  • ResponseEntity.created(URI uri)
    • 201응답과 함께 주어진 URI를 Location헤더로 가지는 빌더
    • URI를 만들기 위해 LinkTo()사용
    • linkTo()
      • 컨트롤러나 핸들러 메소드에서 URI정보를 읽어올 떄 쓰는 메소드
      • Spring HATEOAS 프로젝트에서 제공하는 유틸리티
    • @RequestBody로 받는 부분때문에 methodOn에 createEvent(null)로 처리
    • 이게 귀찮거나 싫으면 다음과 같이 처리할 수도 있다
도메인 비지니스 변형 방법 제시
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {

@PostMapping
public ResponseEntity createEvent(@RequestBody Event event) {
URI uri = linkTo(EventController.class)
.slash("{id}").toUri();
event.setId(10);
return ResponseEntity.created(uri)
.body(event);
}
}
  • 설명

    • mapaping url value가 클래스에 붙어 있으므로 이제 methodOn 필요없이
      바로 클래스에서 이렇게 긁어올 수도 있음
    • 테스트 성공
  • id를 아직 DB에서 가져오지 않음

DB Repository 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Event {

@Id @GeneratedValue
private Integer id;
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location; //(optional), 이게 없으면 온라인 모임
private int basePrice; //(optional)
private int maxPrice; //(optional)
private int limitOfEnrollment;
private boolean offline;
private boolean free;
@Enumerated(EnumType.STRING)
private EventStatus eventStatus = EventStatus.DRAFT;
}
  • 설명
    • 엔티티 클래스로 만들기 위해 @Entity를 붙임
    • Enumerated로 EnumType을 String으로
      • 기본값은 EnumType.ORDINAL으로 순서대로 숫자값 0,1,2로 저장
      • 이 경우 나중에 Enum의 순서가 바뀐 경우 기존 데이터가 완전히 꼬일 수가 있음
    • EventStatus의 기본값은 EventStatus.DRAFT
JpaRepository 생성
1
2
3
import org.springframework.data.jpa.repository.JpaRepository;

public interface EventRepository extends JpaRepository<Event, Integer> {}

EventController에서 JpaRepository 주입 받는 방법

  1. Autowired
  2. 생성자 방법
  • 생성자 주입 방법시, 파라미터가 이미 빈으로 등록되어있고, 생성자가 1개라면
    @Autowired 생략이 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @RestController
    @RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
    public class EventController {

    private final EventRepository eventRepository;

    //@Autowired 생략
    public EventController(EventRepository eventRepository) {
    this.eventRepository = eventRepository;
    }


    @PostMapping
    public ResponseEntity createEvent(@RequestBody Event event) {
    Event resultEvent = this.eventRepository.save(event);

    URI uri = linkTo(EventController.class)
    .slash(resultEvent.getId()).toUri();
    //event.setId(10);
    return ResponseEntity.created(uri)
    .body(resultEvent);
    }
    }
  • 설명
    • 위에서 말했던 생성자 주입시 조건을 만족해서 @Autowired를 생략 가능
    • eventRepository 리턴인 resultEvent는 id를 가지고 있다

테스트 실패 이유

  • @WebMvcTest는 웹관련 빈들만 등록
  • @MockBean을 이용해서 EventRepository를 Mocking할 수 있다
  • 기존 빈을 MockBean이 대체하므로 동작 지정(stubbing)을 해주어야한다
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
55
56
57
58
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {

@Autowired
MockMvc mockMvc;

@Autowired
ObjectMapper objectMapper;

@MockBean
EventRepository eventRepository;

@Test
public void createEvent() throws Exception {

Event event = new Event().builder()
.name("springname")
.description("spring rest api")
.beginEnrollmentDateTime(LocalDateTime.of(2019,3,20,11,53))
.closeEnrollmentDateTime(LocalDateTime.of(2019,3,21,11,55))
.beginEventDateTime(LocalDateTime.of(2019,3,22,11,53))
.endEventDateTime(LocalDateTime.of(2019,3,23,11,53))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남")
.build();

Event resultEvent = new Event().builder()
.name("springname")
.description("spring rest api")
.beginEnrollmentDateTime(LocalDateTime.of(2019,3,20,11,53))
.closeEnrollmentDateTime(LocalDateTime.of(2019,3,21,11,55))
.beginEventDateTime(LocalDateTime.of(2019,3,22,11,53))
.endEventDateTime(LocalDateTime.of(2019,3,23,11,53))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남")
.id(10) //id 추가1
.build();

Mockito.when(eventRepository.save(event)).thenReturn(resultEvent);

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())
.andExpect(header().exists(HttpHeaders.LOCATION))
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE))
;
}
}
  • 설명
    • mockito.when().thenReturn으로 stubbing을 했다
    • id없는 event를 넣을시 id 있는 resultEvent 가 반환되도록
    • 문자열보다는 이미 정의된 상수릃 써서 헤더나 미디아타입을 쓰면 오타위험도 적음

입력값 제한

생성되어야하는 id, 그리고 특정값의 계산에 듸한 상태값인 free,offline등의
boolean값이 getter,setter전체 세팅하였으므로 다 값이 입력가능함

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data @Builder @NoArgsConstructor @AllArgsConstructor
public class EventDto {
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location;
private int basePrice;
private int maxPrice;
private int limitOfEnrollment;
}

DTO를 사용하는 이유

  • 엔티티에 너무 과한 애노테이션

    • 현재도 롬복과 persistence 관련 애노테이션
    • JSR303 Validation까지 추가된다면 너무 과해짐
  • DTO에선 @Data 사용 가능

  • ModelMapper

    • 도메인과 DTO끼리의 값 복사를 손쉽게
    • 리플렉션을 이용해서 미세하게 성능 저하
    • 빈 등록 해서 공용으로 쓰는게 좋을 듯
      ModelMapper 빈 등록
      1
      2
      3
      4
      5
      //@Configuration있는 곳 App.java등
      @Bean
      public ModelMapper modelMapper() {
      return new ModelMapper();
      }
도메인 비지니스 컨트롤러 수정
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
@RestController
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {

private final EventRepository eventRepository;
private final ModelMapper modelMapper;

public EventController(EventRepository eventRepository, ModelMapper modelMapper) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
}


@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto) {

Event event = modelMapper.map(eventDto,Event.class);
Event resultEvent = this.eventRepository.save(event);

URI uri = linkTo(EventController.class)
.slash(resultEvent.getId()).toUri();

return ResponseEntity.created(uri)
.body(resultEvent);
}
}
  • 설명
    • 빈으로 등록된 modelMapper 받아 옴
    • @RequestBody로 EventDto로 받아옴.
    • EventDto->Entity로 값 복사. 이제 DTO에 없는 값들은 Entity로 안들어감
테스트 코드
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
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTest {

@Autowired
MockMvc mockMvc;

@Autowired
ObjectMapper objectMapper;

@Test
public void createEvent() throws Exception {

Event event = new Event().builder()
.id(777)
.name("springname")
.description("spring rest api")
.beginEnrollmentDateTime(LocalDateTime.of(2019,3,20,11,53))
.closeEnrollmentDateTime(LocalDateTime.of(2019,3,21,11,55))
.beginEventDateTime(LocalDateTime.of(2019,3,22,11,53))
.endEventDateTime(LocalDateTime.of(2019,3,23,11,53))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남")
.free(true)
.offline(false)
.eventStatus(EventStatus.PUBLISHED)
.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())
.andExpect(header().exists(HttpHeaders.LOCATION))
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE))
.andExpect(jsonPath("id").value(Matchers.not(777)))
.andExpect(jsonPath("free").value(false))
.andExpect(jsonPath("eventStatus").value(EventStatus.DRAFT.name()))
;
}
}
  • 설명

    • 더이상 Mocking이 불가능
      • Mocking Stubbing에 event객체가 들어왔을시 그 event를 return하도록 stubbing
      • 실제 Respository에서 save된것은 DTO를 거쳐서 만들어진 새로운 객체이므로
        event가 아니라고 판단되서 thenReturn에서 null반환해서 null예외 발생
      • 슬라이싱 테스트가 안됨
    • @SpringbootTest 적용
      • @SpringbootTest 애노테이션 정의를 열어보면 WebEnvironment라는 파라미터가
        있는데 기본값이 SpringBootTest.WebEnvironment.MOCK임
      • Mocking을 한 DispatcherServlet이 만들어짐 ->계속해서 MockMvc 작성 가능
    • @AutoConfigureMockMvc
  • 입력값 이외의 에러 발생

    • 위 Test에서는 그냥 들어오는 값을 무시했지만 만약 DTO에서 정의한 이외의 값이
      들어왔을때 에러로 처리 하고 싶은 경우
objectMapper 커스터마이징
1
spring.jackson.deserialization.fail-on-unknown-properties=true
  • 설명

    • 모르는 프로퍼티가 오는 경우 실패처리 -> 스프링은 400에러 처리
  • 어느게 낫나

    • 받기로 한 값이외에 무시하는 방법
    • BadRequest(400)에러 처리하는 방법
  • 상황에 따라서 그때 그때 맞춰서 ?

    • 개발시에 느슨하게? –> 클라이언트가 잘못된 값도 받을수 있나 오해의 소지가 있음
    • 400으로 하는게 좀더 견고하게

Validation

JSR303 빈 검증 애노테이션 추가한 엔티티
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data @Builder @NoArgsConstructor @AllArgsConstructor
public class EventDto {

@NotEmpty private String name;

@NotEmpty private String description;

@NotNull private LocalDateTime beginEnrollmentDateTime;

@NotNull private LocalDateTime closeEnrollmentDateTime;

@NotNull private LocalDateTime beginEventDateTime;

@NotNull private LocalDateTime endEventDateTime;

@NotEmpty private String location;

@Min(0) private int basePrice;

@Min(0) private int maxPrice;

@Min(0) private int limitOfEnrollment;
}
@Valid 붙인 객체 옆에 있는 Errors로 에러가 들어간다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) {
if(errors.hasErrors()){
errors.getAllErrors().forEach(objectError -> {
log.info("**ERROR: "+objectError);
});
return ResponseEntity.badRequest().build();
}

Event event = modelMapper.map(eventDto,Event.class);
Event resultEvent = this.eventRepository.save(event);

URI uri = linkTo(EventController.class)
.slash(resultEvent.getId()).toUri();

return ResponseEntity.created(uri)
.body(resultEvent);
}
}

값 이상 처리

  • 타입은 맞으나 값이 이상한 경우
    • 예를들어 시작가(basePrice)가 상한가(maxPrice)보다 큰 경우
    • 이벤트 시작(beginEventDateTime)이 종료(endEventDateTime)보다 뒤
  • 이런 경우 어떻게 검증? 애노테이션으로는 불가능
    –> 따로 커스텀 Validator가 필요
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
@Component
public class EventValidator {

public void validate(EventDto eventDto, Errors errors) {

if (eventDto.getBasePrice() > eventDto.getMaxPrice() && eventDto.getMaxPrice() != 0) {
errors.rejectValue("basePrice", "wrongValue", "BasePrices is wrong");
errors.rejectValue("maxPrice", "wrongValue", "MaxPrices is wrong");
}

LocalDateTime beginEnrollmentDateTime = eventDto.getBeginEnrollmentDateTime();
LocalDateTime closeEnrollmentDateTime = eventDto.getCloseEnrollmentDateTime();
LocalDateTime beginEventDateTime = eventDto.getBeginEventDateTime();
LocalDateTime endEventDateTime = eventDto.getEndEventDateTime();

//endEventDateTime 검증
if(endEventDateTime.isBefore(beginEventDateTime) ||
endEventDateTime.isBefore(closeEnrollmentDateTime) ||
endEventDateTime.isBefore(beginEnrollmentDateTime)
){
errors.rejectValue("endEventDateTime","wrongValue","endEventDateTime is wrong");
}

//beginEventDateTime valid
if(beginEventDateTime.isBefore(closeEnrollmentDateTime) ||
beginEventDateTime.isBefore(beginEnrollmentDateTime)
){
errors.rejectValue("beginEventDateTime","wrongValue","BeginEventDateTime is wrong");
}

//CloseEnrollmentDateTime
if(closeEnrollmentDateTime.isBefore(beginEnrollmentDateTime)){
errors.rejectValue("closeEnrollmentDateTime","wrongValue","CloseEnrollmentDateTime is wrong");
}

//beginEnrollmentDateTime 필여 없을 것 같음
}
}
  • 설명
    • Validator 를 상속받지 않은 Validator
    • 각 조건시 errors에 에러를 담았다
    • 이 빈을 컨트롤러에서 주입받은 후 validate후 errors안에 error가 있으면
      BadRequest로 리턴한다

BadRequest 응답 본문 생성

  • 현재 BadRequest이지만 응답본문엔 비어있음
  • 응답본문에 에러를 실어서 보낼 수 있는가?
  • Event객체처럼 ResponseEntity.의 body에 실어서? 불가능
    • 현재 스프링 부트가 기본적으로 등록하는 jackson과 objectMapper 이용 중
    • Event는 자바 빈 스펙을 준수했기 때문에 BeanSerializer로 serialization 가능
    • Errors는 자바 빈 스펙을 준수하지 않음 -> 커스텀 Serializer 필요
      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
      @JsonComponent
      public class ErrorsSerializer extends JsonSerializer<Errors> {
      @Override
      public void serialize(Errors errors, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {

      gen.writeStartArray();

      //field 에러 처리
      errors.getFieldErrors().forEach(e -> {
      try {
      gen.writeStartObject();
      gen.writeStringField("field",e.getField());
      gen.writeStringField("objectName",e.getObjectName());
      gen.writeStringField("code",e.getCode());
      gen.writeStringField("defaultMessage",e.getDefaultMessage());
      //reject된 value가 있을수도 없을수도 있으면 받아온다
      Object rejectedValue = e.getRejectedValue();
      if(rejectedValue!=null){
      gen.writeStringField("rejectedValue", rejectedValue.toString());
      }
      gen.writeEndObject();
      } catch (IOException e1) {
      e1.printStackTrace();
      }
      });

      //gobal error
      //테스트에서 에러가 날 수 있으므로 일단 주석 처리
      errors.getGlobalErrors().forEach(e -> {
      try {
      gen.writeStartObject();
      gen.writeStringField("objectName",e.getObjectName());
      gen.writeStringField("code",e.getCode());
      gen.writeStringField("defaultMessage",e.getDefaultMessage());
      gen.writeEndObject();
      } catch (IOException e1) {
      e1.printStackTrace();
      }
      });

      gen.writeEndArray();
      }
      }

비지니스 로직

  • Location 값이 있으면 offline 상태는 true여야함

  • basePrice와 maxPrice가 둘다 0이면 상태 free 상태는 true여야함

  • 수정

    • Event.update()에서 위의 로직 반영
    • Controller에서 ModelMapper에서 DTO에서 값을 엔티티 매핑 후 update()실행
    • 로직이 많다면 update()와 JPA의 save까지 서비스 레이어 만들어서 이전

String isBlank()

  • 자바 11부터 추가
  • 이전 버전에선 trim 이후 isEmpty()를 사용
  • 스페이스 외의 공백 문자열까지 확인 해줌

매개변수를 이용한 테스트

테스트 리팩토링 필요성

  • 같은 테스트 코드에 특정 값에 따라 기대하는 true,false만 다를 때 꼼꼼한 테스트를
    위해 많은 코드들이 반복됨
  • JUnitParams - 매개변수만 교체 가능
    • 원래 JUnit은 메소드에 파라미터를 가질 수가 없음
    • 사용시 파라미터 사용해서 가능
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      @RunWith(JUnitParamsRunner.class)
      public class EventTest {

      @Test
      @Parameters({
      "0,0,true",
      "100,0,false",
      "0,100,false"
      })
      public void testFree(int basePrice, int maxPrice, boolean Expected ) {
      //Given
      Event event = Event.builder()
      .basePrice(basePrice)
      .maxPrice(maxPrice)
      .build();

      //When
      event.update();

      // Then
      assertThat(event.isFree()).isEqualTo(Expected);
      }
      }
  • 설명
    • Runner로 JUnitParamsRunner를 사용
    • @Parameters 밑의 값이 메소드의 매개변수와 매칭
    • 3개의 반복될 코드를 훌륭하게 제거

타입 세이프가능?: 현재 파라미터가 문자열이므로 타입 세이프하지 않음

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
@Test
@Parameters
public void testFree(int basePrice, int maxPrice, boolean Expected ) {
//Given
Event event = Event.builder()
.basePrice(basePrice)
.maxPrice(maxPrice)
.build();

//When
event.update();

// Then
assertThat(event.isFree()).isEqualTo(Expected);

}


private Object[] parametersForTestFree() {
return new Object[]{
new Object[]{0, 0, true},
new Object[]{100, 0, false},
new Object[]{0, 100, false},
new Object[]{100, 200, false}
};
}
  • 설명
    • @Parameters에 method 이름 설정 가능
    • 컨벤션: parametersFor 접두어가 컨벤션으로 컨벤션뒤 메소드명과 자동매칭

Related POST

공유하기