Skip to content

Commit

Permalink
[JT-73] 회차 서비스 테스트
Browse files Browse the repository at this point in the history
  • Loading branch information
hongdosan authored Sep 24, 2023
2 parents b185e02 + db64810 commit 8dca9bd
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import static shop.jtoon.common.ImageType.*;
import static shop.jtoon.type.ErrorStatus.*;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import lombok.RequiredArgsConstructor;
import shop.jtoon.dto.UploadImageDto;
import shop.jtoon.entity.Episode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import shop.jtoon.common.FileName;
import shop.jtoon.common.ImageType;
import shop.jtoon.dto.UploadImageDto;
import shop.jtoon.entity.Episode;
import shop.jtoon.entity.Webtoon;

@Builder
public record CreateEpisodeReq(
@Min(1) int no,
@NotBlank @Size(max = 30) String title,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package shop.jtoon.webtoon.request;

import lombok.Getter;
import lombok.experimental.SuperBuilder;
import shop.jtoon.type.CustomPageRequest;

@Getter
@SuperBuilder
public class GetEpisodesReq extends CustomPageRequest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package shop.jtoon.webtoon.application;

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

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
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 org.springframework.mock.web.MockMultipartFile;

import shop.jtoon.dto.UploadImageDto;
import shop.jtoon.entity.Episode;
import shop.jtoon.entity.Member;
import shop.jtoon.entity.PurchasedEpisode;
import shop.jtoon.entity.Webtoon;
import shop.jtoon.exception.DuplicatedException;
import shop.jtoon.exception.InvalidRequestException;
import shop.jtoon.exception.NotFoundException;
import shop.jtoon.member.application.MemberService;
import shop.jtoon.payment.application.MemberCookieService;
import shop.jtoon.repository.EpisodeRepository;
import shop.jtoon.repository.EpisodeSearchRepository;
import shop.jtoon.repository.PurchasedEpisodeRepository;
import shop.jtoon.response.EpisodeInfoRes;
import shop.jtoon.response.EpisodeItemRes;
import shop.jtoon.service.S3Service;
import shop.jtoon.webtoon.factory.CreatorFactory;
import shop.jtoon.webtoon.request.CreateEpisodeReq;
import shop.jtoon.webtoon.request.GetEpisodesReq;

@ExtendWith(MockitoExtension.class)
class EpisodeServiceTest {

@InjectMocks
private EpisodeService episodeService;

@Mock
private MemberService memberService;

@Mock
private MemberCookieService memberCookieService;

@Mock
private WebtoonService webtoonService;

@Mock
private S3Service s3Service;

@Mock
private EpisodeRepository episodeRepository;

@Mock
private EpisodeSearchRepository episodeSearchRepository;

@Mock
private PurchasedEpisodeRepository purchasedEpisodeRepository;

private Member member;
private Webtoon webtoon;

@BeforeEach
void beforeEach() {
member = spy(CreatorFactory.createMember());
lenient().when(member.getId()).thenReturn(1L);
webtoon = spy(CreatorFactory.createWebtoon(member));
lenient().when(webtoon.getId()).thenReturn(1L);
}

@DisplayName("createEpisode - 회차 생성 성공, - Void")
@Test
void createEpisode_Void() {
// Given
CreateEpisodeReq request = CreatorFactory.createEpisodeReq();
MockMultipartFile image = CreatorFactory.createMultipartFile();
given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon);
given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("https://webtoons/episodes/image");

// When
episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request);

// Then
verify(episodeRepository).save(any(Episode.class));
}

@DisplayName("createEpisode - 회차 번호 중복, - DuplicatedException")
@Test
void createEpisode_DuplicatedException() {
// Given
CreateEpisodeReq request = CreatorFactory.createEpisodeReq();
MockMultipartFile image = CreatorFactory.createMultipartFile();
given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon);
given(episodeRepository.existsByWebtoonAndNo(any(Webtoon.class), anyInt())).willReturn(true);

// When, Then
assertThatThrownBy(() -> episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request))
.isInstanceOf(DuplicatedException.class)
.hasMessage("이미 존재하는 회차 번호입니다.");
}

@DisplayName("createEpisode - 회차 생성 실패 시 이미지 삭제 서비스 호출, - InvalidRequestException")
@Test
void createEpisode_InvalidRequestException() {
// Given
CreateEpisodeReq request = CreatorFactory.createEpisodeReq();
MockMultipartFile image = CreatorFactory.createMultipartFile();
given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon);
given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("https://webtoons/episodes/image");
given(episodeRepository.save(any(Episode.class))).willThrow(new RuntimeException());

// When, Then
assertThatThrownBy(() -> episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request))
.isInstanceOf(InvalidRequestException.class)
.hasMessage("회차 생성에 실패했습니다.");
verify(s3Service, times(2)).deleteImage(anyString());
}

@DisplayName("getEpisodes - 조회할 회차 리스트가 없을 때, - Empty List")
@Test
void getEpisodes_EmptyList() {
// Given
GetEpisodesReq request = CreatorFactory.createGetEpisodesReq();
List<Episode> episodes = new ArrayList<>();
given(episodeSearchRepository.getEpisodes(webtoon.getId(), request.getSize(), request.getOffset()))
.willReturn(episodes);

// When
List<EpisodeItemRes> actual = episodeService.getEpisodes(webtoon.getId(), request);

// Then
assertThat(actual).isEmpty();
}

@DisplayName("getEpisodes - 회차 리스트 조회 성공, - List<EpisodeItemRes>")
@Test
void getEpisodes_EpisodeItemResList() {
// Given
GetEpisodesReq request = CreatorFactory.createGetEpisodesReq();
List<Episode> episodes = new ArrayList<>();
episodes.add(CreatorFactory.createEpisode(webtoon, 1));
episodes.add(CreatorFactory.createEpisode(webtoon, 2));
given(episodeSearchRepository.getEpisodes(webtoon.getId(), request.getSize(), request.getOffset()))
.willReturn(episodes);

// When
List<EpisodeItemRes> actual = episodeService.getEpisodes(webtoon.getId(), request);

// Then
assertThat(actual).hasSize(episodes.size());
}

@DisplayName("getEpisode - 회차 정보가 존재하지 않을 때, - NotFoundException")
@Test
void getEpisode_NotFoundException() {
// Given
given(episodeRepository.findById(anyLong())).willReturn(Optional.empty());

// When, Then
assertThatThrownBy(() -> episodeService.getEpisode(1L))
.isInstanceOf(NotFoundException.class)
.hasMessage("존재하지 않는 회차입니다.");
}

@DisplayName("getEpisode - 회차 정보 조회 성공, - EpisodeInfoRes")
@Test
void getEpisode_EpisodeInfoRes() {
// Given
Episode episode = CreatorFactory.createEpisode(webtoon, 1);
given(episodeRepository.findById(anyLong())).willReturn(Optional.of(episode));

// When
EpisodeInfoRes actual = episodeService.getEpisode(1L);

// Then
assertThat(actual.mainUrl()).isEqualTo("https://webtoons/episodes/main");
}

@DisplayName("purchaseEpisode - 회차 구매 성공, - Void")
@Test
void purchaseEpisode_Void() {
// Given
Episode episode = spy(CreatorFactory.createEpisode(webtoon, 1));
given(episode.getId()).willReturn(1L);
given(memberService.findById(member.getId())).willReturn(member);
given(episodeRepository.findById(anyLong())).willReturn(Optional.of(episode));

// When
episodeService.purchaseEpisode(member.getId(), episode.getId());

// Then
verify(memberCookieService).useCookie(episode.getCookieCount(), member);
verify(purchasedEpisodeRepository).save(any(PurchasedEpisode.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package shop.jtoon.webtoon.factory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;

import org.springframework.mock.web.MockMultipartFile;

import shop.jtoon.entity.Episode;
import shop.jtoon.entity.Gender;
import shop.jtoon.entity.LoginType;
import shop.jtoon.entity.Member;
import shop.jtoon.entity.Role;
import shop.jtoon.entity.Webtoon;
import shop.jtoon.entity.enums.AgeLimit;
import shop.jtoon.webtoon.request.CreateEpisodeReq;
import shop.jtoon.webtoon.request.GetEpisodesReq;

public class CreatorFactory {

public static Member createMember() {
return Member.builder()
.email("test@gmail.com")
.password("Test123!")
.name("홍길동")
.nickname("길동")
.gender(Gender.MALE)
.phone("01012345678")
.role(Role.USER)
.loginType(LoginType.LOCAL)
.build();
}

public static Webtoon createWebtoon(Member member) {
return Webtoon.builder()
.title("웹툰 제목")
.description("웹툰 설명")
.ageLimit(AgeLimit.ALL)
.thumbnailUrl("https://webtoons/thumbnail")
.cookieCount(3)
.author(member)
.build();
}

public static Episode createEpisode(Webtoon webtoon, int no) {
return Episode.builder()
.no(no)
.title("회차 제목")
.mainUrl("https://webtoons/episodes/main")
.thumbnailUrl("https://webtoons/episodes/thumbnail")
.hasComment(true)
.openedAt(LocalDateTime.of(2023, 9, 20, 0, 0, 0))
.webtoon(webtoon)
.build();
}

public static CreateEpisodeReq createEpisodeReq() {
return CreateEpisodeReq.builder()
.no(1)
.title("회차 제목")
.hasComment(true)
.openedAt(LocalDateTime.of(2023, 9, 20, 0, 0, 0))
.build();
}

public static GetEpisodesReq createGetEpisodesReq() {
return GetEpisodesReq.builder().build();
}

public static MockMultipartFile createMultipartFile() {
Path path = Paths.get("src/test/resources/test.png");

try {
return new MockMultipartFile(
"image",
"test.png",
"image/png",
Files.readAllBytes(path)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

@Getter
@Setter
@Builder
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class CustomPageRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,16 @@ public class Member extends BaseTimeEntity {
private LocalDateTime lastLoginDate;

@Builder
private Member(String email, String password, String name, String nickname, Gender gender, String phone, Role role,
LoginType loginType) {
private Member(
String email,
String password,
String name,
String nickname,
Gender gender,
String phone,
Role role,
LoginType loginType
) {
this.email = requireNonNull(email, ErrorStatus.MEMBER_EMAIL_INVALID_FORMAT.getMessage());
this.password = requireNonNull(password, ErrorStatus.MEMBER_PASSWORD_INVALID_FORMAT.getMessage());
this.name = requireNonNull(name, ErrorStatus.MEMBER_NAME_INVALID_FORMAT.getMessage());
Expand All @@ -85,6 +93,6 @@ public void updateLastLogin() {
}

public boolean isEqual(Long memberId) {
return Objects.equals(this.id, memberId);
return Objects.equals(this.getId(), memberId);
}
}

0 comments on commit 8dca9bd

Please sign in to comment.