[Spring REST API #8] Spring REST API 비즈니스 로직 적용 및 JUnitParam을 이용한 테스트

2021. 3. 26. 02:45 Spring Data/Spring Data REST

 

Spring REST API 비즈니스 로직 적용 및  JUnitParam을 이용한 테스트

 

JUnitParam 테스트 라이브러리는 JUnit의 각 Test 메서드에 파라미터를 쓸 수 있게 함으로써 테스트 코드량을 줄이고 유지보수를 쉽게 해주는 유용한 라이브러리입니다. 이 JUnitParam 라이브러리를 이용하여 아래와 같은 비즈니스 로직을 적용한 코드를 작성하도록 하겠습니다.

 

  • basePrice 와 maxPrice가 모두 0이면 free = true 그 이외에는 free = false
  • location이 null 이거나 문자열의 길이가 0일 때 offline = false 그 외에는 offline = true

 

모든 소스 코드는 여기에서 보실 수 있습니다.

 

프로젝트 구조

+---src
|   +---main
|   |   +---java
|   |   |   \---com
|   |   |       \---example
|   |   |           \---springrestapi
|   |   |               |   SpringRestApiApplication.java
|   |   |               |
|   |   |               +---common
|   |   |               |       ErrorsSerializer.java
|   |   |               |       TestDescription.java
|   |   |               |
|   |   |               \---events
|   |   |                       Event.java
|   |   |                       EventController.java
|   |   |                       EventDto.java
|   |   |                       EventRepository.java
|   |   |                       EventStatus.java
|   |   |                       EventValidator.java
|   |   |
|   |   \---resources
|   |       |   application.yml
|   |       |
|   |       +---static
|   |       \---templates

 

의존성 관리

<dependencies>
		...
	<dependency>
		<groupId>pl.pragmatists</groupId>
		<artifactId>JUnitParams</artifactId>
		<version>1.1.1</version>
		<scope>test</scope>
	</dependency>
</dependencies>
  • JUnitParam을 사용하기 위해서는 위와 같이 의존성을 추가해야 합니다.

 

테스트 코드

package com.example.springrestapi.events;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(JUnitParamsRunner.class)
public class EventTest {

    // ...

    @Test
    @Parameters
    //@Parameters(method = "parametersForTestFree")  // You can reference a parameter method directly like leftward
    public void testFree(int basePrice, int maxPrice, boolean isFree) {
        // Given
        Event event = Event.builder().
                basePrice(basePrice)
                .maxPrice(maxPrice)
                .build();

        // When
        event.update();

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

    private Object[] parametersForTestFree() {
        return new Object[] {
            new Object[] {0, 0, true},
            new Object[] {100, 0, false},
            new Object[] {0, 100, false},
        };
    }

    @Test
    @Parameters
    public void testOffline(String location, boolean isOffline) {
        // Given
        Event event = Event.builder()
                .location("StartUp Factory")
                .build();

        // When
        event.update();

        // Then
        assertThat(event.isOffline()).isTrue();
    }

    private Object[] parametersForTestOffline() {
        return new Object[] {
                new Object[] {"강남", true},
                new Object[] {null, false},
                new Object[] {"   ", false},
        };
    }
}
  • JUnitParams를 적용하여 테스트 코드를 작성한 것을 볼 수 있습니다. @Parameters 어노테이션을 각 테스트에 명시하면 파리미터화 된 입력값을 쓸 수 있습니다.
  • 파라미터화된 입력값은 위 코드와 같이 코드 상에 명시해야 하며 parametersFor + [테스트명] 으로 작성하면 자동적으로 [테스트명]으로 작성된 테스트 메서드에 해당 입력값이 매칭되게 됩니다. 또한 @Parameters(method="[테스트명]") 형태로 직정 지정해서 쓸 수 있습니다.

 

소스 코드

package com.example.springrestapi.events;

import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;


/**
 * In case of referencing between entities, using default @EqualsAndHashCode stack overflow.
 * Therefore, redefine the way of checking equality is the best practice as below.
 * Also, you shouldn't use @Data annotation because it uses default @EqualsAndHashCode
 */
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(of = "id")
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;

    public void update() {
        // Update free
        if(this.basePrice == 0 && this.maxPrice == 0) {
            this.free = true;
        } else {
            this.free = false;
        }

        // Update offline
        if(this.location == null || this.location.isBlank()) {
            this.offline = false;
        } else {
            this.offline = true;
        }
    }
}
  • 위에서 언급한 비즈니스 로직을 적용한 엔티티 클래스입니다. 

package com.example.springrestapi.events;

import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;
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 {

    @Autowired
    EventRepository eventRepository;

    @Autowired
    ModelMapper modelMapper;

    @Autowired
    EventValidator eventValidator;

    @PostMapping
    public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) {
        if(errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors);
        }

        eventValidator.validate(eventDto, errors);
        if(errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors);
        }

        Event event = modelMapper.map(eventDto, Event.class);
        event.update();
        Event newEvent = this.eventRepository.save(event);
        URI createdURI = linkTo(EventController.class).slash(newEvent.getId()).toUri();
        return ResponseEntity.created(createdURI).body(newEvent);
    }
}
  • HTTP 요청 안에 포함된 데이터들을 Event 객체로 역직렬화한 다음 Event 객체의 update 메서드를 이용해 비즈니스 로직을 적용한 모습입니다

 

결과 화면

 

 

참조: https://www.inflearn.com/course/spring_rest-api/#

소스 코드 : https://github.com/engkimbs/spring-rest-api



출처: https://engkimbs.tistory.com/863?category=789178 [새로비]