Skip to content

Commit

Permalink
test: 쿠폰 발행 기능 테스트
Browse files Browse the repository at this point in the history
  • Loading branch information
hongdosan committed Nov 8, 2023
1 parent 704777f commit d40b614
Show file tree
Hide file tree
Showing 11 changed files with 816 additions and 4 deletions.
35 changes: 35 additions & 0 deletions src/docs/asciidoc/coupon.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
== 쿠폰(Coupon)

쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다.

=== 쿠폰 생성

관리자가 쿠폰을 생성합니다.

[discrete]
==== 요청

include::{snippets}/coupons/http-request.adoc[]

[discrete]
==== 응답

include::{snippets}/coupons/http-response.adoc[]

=== 쿠폰 삭제 (진행 중)

관리자가 쿠폰을 삭제합니다.

=== 쿠폰 조회 (진행 중)

관리자 혹은 사용자가 쿠폰들을 조회합니다.

사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다.

=== 쿠폰 발급 (진행 중)

사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다.

=== 쿠폰 사용 (진행 중)

사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
2 changes: 2 additions & 0 deletions src/main/java/com/moabam/api/dto/CreateCouponRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;

@Builder
public record CreateCouponRequest(
@NotBlank(message = "쿠폰명이 입력되지 않았거나 20자를 넘었습니다.") @Length(max = 20) String name,
@Length(max = 50, message = "쿠폰 간단 소개는 최대 50자까지 가능합니다.") String description,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.moabam.api.presentation;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.moabam.api.application.CouponService;
Expand All @@ -18,6 +20,7 @@ public class CouponController {
private final CouponService couponService;

@PostMapping
@ResponseStatus(HttpStatus.OK)
public void createCoupon(@RequestBody CreateCouponRequest request) {
couponService.createCoupon(1L, request);
}
Expand Down
540 changes: 540 additions & 0 deletions src/main/resources/static/docs/coupon.html

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions src/test/java/com/moabam/api/application/CouponServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.moabam.api.application;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.moabam.api.domain.entity.Coupon;
import com.moabam.api.domain.entity.enums.CouponType;
import com.moabam.api.domain.repository.CouponRepository;
import com.moabam.api.dto.CreateCouponRequest;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.exception.ConflictException;
import com.moabam.global.error.exception.NotFoundException;
import com.moabam.global.error.model.ErrorMessage;
import com.moabam.support.fixture.CouponFixture;

@ExtendWith(MockitoExtension.class)
class CouponServiceTest {

@InjectMocks
private CouponService couponService;

@Mock
private CouponRepository couponRepository;

@DisplayName("쿠폰을 성공적으로 발행한다. - Void")
@Test
void couponService_createCoupon() {
// Given
String couponType = CouponType.GOLDEN_COUPON.getTypeName();
CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2);

given(couponRepository.existsByName(any(String.class))).willReturn(false);

// When
couponService.createCoupon(1L, request);

// Then
verify(couponRepository).save(any(Coupon.class));
}

@DisplayName("중복된 쿠폰명을 발행한다. - ConflictException")
@Test
void couponService_createCoupon_ConflictException() {
// Given
String couponType = CouponType.GOLDEN_COUPON.getTypeName();
CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2);

given(couponRepository.existsByName(any(String.class))).willReturn(true);

// When & Then
assertThatThrownBy(() -> couponService.createCoupon(1L, request))
.isInstanceOf(ConflictException.class)
.hasMessage(ErrorMessage.CONFLICT_COUPON_NAME.getMessage());
}

@DisplayName("존재하지 않는 쿠폰 종류를 발행한다. - NotFoundException")
@Test
void couponService_createCoupon_NotFoundException() {
// Given
CreateCouponRequest request = CouponFixture.createCouponRequest("UNKNOWN", 1, 2);
given(couponRepository.existsByName(any(String.class))).willReturn(false);

// When & Then
assertThatThrownBy(() -> couponService.createCoupon(1L, request))
.isInstanceOf(NotFoundException.class)
.hasMessage(ErrorMessage.NOT_FOUND_COUPON_TYPE.getMessage());
}

@DisplayName("쿠폰 발급 종료 기간이 시작 기간보다 더 이전인 쿠폰을 발행한다. - BadRequestException")
@Test
void couponService_createCoupon_BadRequestException() {
// Given
String couponType = CouponType.GOLDEN_COUPON.getTypeName();
CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 2, 1);
given(couponRepository.existsByName(any(String.class))).willReturn(false);

// When & Then
assertThatThrownBy(() -> couponService.createCoupon(1L, request))
.isInstanceOf(BadRequestException.class)
.hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage());
}
}
4 changes: 2 additions & 2 deletions src/test/java/com/moabam/api/domain/entity/CouponTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class CouponTest {

@DisplayName("쿠폰 보너스 포인트가 0보다 작을 때, - BadRequestException")
@DisplayName("쿠폰 보너스 포인트가 1보다 작다. - BadRequestException")
@Test
void coupon_validatePoint_Point_BadRequestException() {
// When& Then
Expand All @@ -20,7 +20,7 @@ void coupon_validatePoint_Point_BadRequestException() {
.hasMessage(ErrorMessage.INVALID_COUPON_POINT.getMessage());
}

@DisplayName("쿠폰 재고가 0보다 작을 때, - BadRequestException")
@DisplayName("쿠폰 재고가 1보다 작다. - BadRequestException")
@Test
void coupon_validatePoint_Stock_BadRequestException() {
// When& Then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class CouponTypeTest {

@DisplayName("존재하는 쿠폰을 가져오려고 할 때, - CouponType")
@DisplayName("존재하는 쿠폰을 가져온다. - CouponType")
@Test
void couponType_from() {
// When
Expand All @@ -20,7 +20,7 @@ void couponType_from() {
assertThat(actual).isEqualTo(CouponType.GOLDEN_COUPON);
}

@DisplayName("존재하지 않는 쿠폰을 가져오려고 할 때, - NotFoundException")
@DisplayName("존재하지 않는 쿠폰을 가져온다. - NotFoundException")
@Test
void couponType_from_NotFoundException() {
// When & Then
Expand Down
31 changes: 31 additions & 0 deletions src/test/java/com/moabam/api/dto/CreateCouponRequestTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.moabam.api.dto;

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

import java.time.LocalDateTime;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

class CreateCouponRequestTest {

@DisplayName("쿠폰 발급 가능 시작 날짜가 올바른 형식으로 입력된다. - yyyy-MM-dd'T'HH:mm")
@Test
void createCouponRequest_StartAt() throws JsonProcessingException {
// Given
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());

String json = "{\"startAt\":\"2023-11-09T10:10\"}";

// When
CreateCouponRequest actual = objectMapper.readValue(json, CreateCouponRequest.class);

// Then
assertThat(actual.startAt()).isEqualTo(LocalDateTime.of(2023, 11, 9, 10, 10));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.moabam.api.presentation;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.moabam.api.domain.entity.enums.CouponType;
import com.moabam.api.domain.repository.CouponRepository;
import com.moabam.api.dto.CouponMapper;
import com.moabam.api.dto.CreateCouponRequest;
import com.moabam.support.fixture.CouponFixture;
import com.moabam.support.fixture.CouponSnippetFixture;

@Transactional
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
class CouponControllerTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@Autowired
private CouponRepository couponRepository;

@DisplayName("쿠폰을 성공적으로 발행한다. - Void")
@Test
void couponController_createCoupon() throws Exception {
// Given
String couponType = CouponType.GOLDEN_COUPON.getTypeName();
CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2);

// When & Then
mockMvc.perform(post("/admins/coupons")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andDo(print())
.andDo(document("coupons",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
CouponSnippetFixture.CREATE_COUPON_REQUEST))
.andExpect(status().isOk());
}

@DisplayName("쿠폰명이 중복된 쿠폰을 발행한다. - ConflictException")
@Test
void couponController_createCoupon_ConflictException() throws Exception {
// Given
String couponType = CouponType.GOLDEN_COUPON.getTypeName();
CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2);
couponRepository.save(CouponMapper.toEntity(1L, request));

// When & Then
mockMvc.perform(post("/admins/coupons")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andDo(print())
.andDo(document("coupons",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
CouponSnippetFixture.CREATE_COUPON_REQUEST))
.andExpect(status().isConflict());
}
}
13 changes: 13 additions & 0 deletions src/test/java/com/moabam/support/fixture/CouponFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.moabam.api.domain.entity.Coupon;
import com.moabam.api.domain.entity.enums.CouponType;
import com.moabam.api.dto.CreateCouponRequest;

public final class CouponFixture {

Expand All @@ -17,4 +18,16 @@ public static Coupon coupon(int point, int stock) {
.endAt(LocalDateTime.now())
.build();
}

public static CreateCouponRequest createCouponRequest(String couponType, int startMonth, int endMonth) {
return CreateCouponRequest.builder()
.name("couponName")
.description("coupon description")
.point(10)
.type(couponType)
.stock(10)
.startAt(LocalDateTime.of(2000, startMonth, 22, 10, 30, 0))
.endAt(LocalDateTime.of(2000, endMonth, 22, 11, 0, 0))
.build();
}
}
19 changes: 19 additions & 0 deletions src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.moabam.support.fixture;

import static org.springframework.restdocs.payload.JsonFieldType.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;

import org.springframework.restdocs.payload.RequestFieldsSnippet;

public final class CouponSnippetFixture {

public static final RequestFieldsSnippet CREATE_COUPON_REQUEST = requestFields(
fieldWithPath("name").type(STRING).description("쿠폰명"),
fieldWithPath("description").type(STRING).description("쿠폰 간단 소개 (NULL 가능)"),
fieldWithPath("type").type(STRING).description("쿠폰 종류 (아침, 저녁, 황금, 할인)"),
fieldWithPath("point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"),
fieldWithPath("stock").type(NUMBER).description("쿠폰을 발급 받을 수 있는 수"),
fieldWithPath("startAt").type(STRING).description("쿠폰 발급 시작 날짜 (Ex: yyyy-MM-dd'T'HH:mm)"),
fieldWithPath("endAt").type(STRING).description("쿠폰 발급 종료 날짜 (Ex: yyyy-MM-dd'T'HH:mm)")
);
}

0 comments on commit d40b614

Please sign in to comment.