Skip to content

feature/#109 Post 테스트 코드 작성 #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package kgu.developers.api.post.application;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kgu.developers.api.post.presentation.exception.PostNotFoundException;
import kgu.developers.api.post.presentation.request.PostRequest;
import kgu.developers.api.post.presentation.response.PostDetailResponse;
Expand All @@ -20,6 +12,13 @@
import kgu.developers.domain.post.domain.PostRepository;
import kgu.developers.domain.user.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Service
@RequiredArgsConstructor
Expand All @@ -35,12 +34,12 @@ public class PostService {
public PostPersistResponse createPost(PostRequest request, Category category) {
User author = userService.me();
Post createPost = Post.create(request.title(), request.content(), category, author);
postRepository.save(createPost);
return PostPersistResponse.from(createPost.getId());
Long id = postRepository.save(createPost).getId();
return PostPersistResponse.from(id);
}

public PostSummaryPageResponse getPostsByKeywordAndCategory(PageRequest request, String keyword,
Category category) {
Category category) {
PaginatedListResponse<Post> paginatedListResponse = postRepository.findAllByTitleContainingAndCategoryOrderByCreatedAtDesc(
keyword, category, request);
return PostSummaryPageResponse.of(paginatedListResponse.contents(), paginatedListResponse.pageable());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Builder;

@Builder
public record PostRequest(
@Schema(description = "게시물 제목", example = "SW 부트캠프 4기 교육생 모집", requiredMode = REQUIRED)
@NotBlank
Expand Down
166 changes: 166 additions & 0 deletions aics-api/src/testFixtures/java/post/application/PostServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package post.application;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import kgu.developers.api.post.application.PostService;
import kgu.developers.api.post.presentation.exception.PostNotFoundException;
import kgu.developers.api.post.presentation.request.PostRequest;
import kgu.developers.api.post.presentation.response.PostDetailResponse;
import kgu.developers.api.post.presentation.response.PostPersistResponse;
import kgu.developers.api.post.presentation.response.PostSummaryPageResponse;
import kgu.developers.api.user.application.UserService;
import kgu.developers.domain.post.domain.Category;
import kgu.developers.domain.post.domain.Post;
import kgu.developers.domain.user.domain.Major;
import kgu.developers.domain.user.domain.User;
import mock.FakePostRepository;
import mock.FakeUserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PostServiceTest {
private PostService postService;

@BeforeEach
public void init() {
FakePostRepository fakePostRepository = new FakePostRepository();
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
FakeUserRepository fakeUserRepository = new FakeUserRepository();
UserService userService = new UserService(bCryptPasswordEncoder, fakeUserRepository);

this.postService = new PostService(fakePostRepository, userService);

fakeUserRepository.save(User.builder()
.id("202411345")
.password(bCryptPasswordEncoder.encode("password1234"))
.name("홍길동")
.email("test@kyonggi.ac.kr")
.phone("010-1234-5678")
.major(Major.CSE)
.build());

UserDetails user = userService.getUserById("202411345");
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(
new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities())
);

fakePostRepository.save(Post.create(
"테스트용 제목1", "테스트용 내용1",
Category.DEPT_INFO, userService.getUserById("202411345")
));

fakePostRepository.save(Post.create(
"테스트용 제목2", "테스트용 내용2",
Category.DEPT_INFO, userService.getUserById("202411345")
));
}

@Test
@DisplayName("createPost는 게시글을 생성할 수 있다")
public void createPost_Success() {
// given
PostRequest request = PostRequest.builder()
.title("테스트용 제목3")
.content("테스트용 내용3")
.build();
Category category = Category.DEPT_INFO;

// when
PostPersistResponse response = postService.createPost(request, category);

// then
assertEquals(3, response.postId());

// when
PostDetailResponse created = postService.getPostById(response.postId());

// then
assertEquals(request.title(), created.title());
assertEquals(request.content(), created.content());
assertEquals(category.getDescription(), created.category());
}

@Test
@DisplayName("getPostById는 게시글을 조회할 수 있다")
public void getPostById_Success() {
// given
Long postId = 1L;

// when
PostDetailResponse response = postService.getPostById(postId);

// then
assertEquals(postId, response.postId());
}

@Test
@DisplayName("getPostById는 존재하지 않는 ID로 조회시 PostNotFoundException을 발생시킨다")
public void getPostById_Throws_PostNotFoundException() {
// given
Long postId = 10L;

// when
// then
assertThatThrownBy(
() -> postService.getPostById(postId)
).isInstanceOf(PostNotFoundException.class);
}

@Test
@DisplayName("getPostsByKeywordAndCategory는 게시글을 페이징 조회할 수 있다")
public void getPostsByKeywordAndCategory_Success() {
// given
String keyword = "제목";
Category category = Category.DEPT_INFO;
int page = 0;
int size = 10;

// when
PostSummaryPageResponse posts = postService.getPostsByKeywordAndCategory(
PageRequest.of(page, size), keyword, category
);

// then
assertEquals(2, posts.contents().size());
}

@Test
@DisplayName("togglePostPinStatus는 게시글의 고정 상태를 변경할 수 있다")
public void togglePostPinStatus_Success() {
// given
Long postId = 1L;
PostDetailResponse before = postService.getPostById(postId);

// when
postService.togglePostPinStatus(postId);

// then
PostDetailResponse after = postService.getPostById(postId);
assertNotEquals(before.isPinned(), after.isPinned());
}

@Test
@DisplayName("deletePost 이후 게시글을 조회할 수 없다")
public void deletePost_Throws_PostNotFoundException() {
// given
Long postId = 1L;

// when
postService.deletePost(postId);

// then
assertThatThrownBy(
() -> postService.getPostById(postId)
).isInstanceOf(PostNotFoundException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package post.presentation;

public class PostControllerTest {
}
87 changes: 87 additions & 0 deletions aics-domain/src/testFixtures/java/mock/FakePostRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package mock;

import kgu.developers.common.response.PageableResponse;
import kgu.developers.common.response.PaginatedListResponse;
import kgu.developers.domain.post.domain.Category;
import kgu.developers.domain.post.domain.Post;
import kgu.developers.domain.post.domain.PostRepository;
import org.springframework.data.domain.Pageable;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

public class FakePostRepository implements PostRepository {
private final List<Post> data = Collections.synchronizedList(new ArrayList<>());
private final AtomicLong autoGeneratedId = new AtomicLong(0);

@Override
public Post save(Post post) {
if (post == null) {
throw new IllegalArgumentException("게시글은 null일 수 없습니다.");
}
if (post.getTitle() == null || post.getContent() == null) {
throw new IllegalArgumentException("제목과 내용은 필수 입력값입니다.");
}

Post newPost = Post.builder()
.id(autoGeneratedId.incrementAndGet())
.title(post.getTitle())
.content(post.getContent())
.views(post.getViews())
.category(post.getCategory())
.author(post.getAuthor())
.isPinned(post.isPinned())
.comments(new ArrayList<>(post.getComments()))
.file(post.getFile())
.build();

TestEntityUtils.setCreatedAt(newPost, LocalDateTime.now());

data.add(newPost);
return newPost;
}

@Override
public PaginatedListResponse<Post> findAllByTitleContainingAndCategoryOrderByCreatedAtDesc(
String keyword, Category category, Pageable pageable
) {
List<Post> filteredPosts = data.stream()
.filter(
post -> post.getTitle().contains(keyword) && post.getCategory().equals(category)
)
.sorted(Comparator.comparing(Post::getCreatedAt).reversed())
.collect(Collectors.toList());

int start = (int) pageable.getOffset();
int end = Math.min(start + pageable.getPageSize(), filteredPosts.size());

List<Post> paginatedPosts = start > filteredPosts.size() ?
Collections.emptyList() :
filteredPosts.subList(start, end);

List<Long> ids = paginatedPosts.stream()
.map(Post::getId)
.toList();

return PaginatedListResponse.of(paginatedPosts, PageableResponse.of(pageable, ids));
}

@Override
public Optional<Post> findById(Long postId) {
return data.stream()
.filter(post -> post.getId().equals(postId))
.findAny();
}

@Override
public void deleteAllByDeletedAtBefore(int retentionDays) {
LocalDateTime threshold = LocalDateTime.now().minusDays(retentionDays);
data.removeIf(post -> post.getDeletedAt() != null && post.getDeletedAt().isBefore(threshold));
}
}
23 changes: 23 additions & 0 deletions aics-domain/src/testFixtures/java/mock/TestEntityUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mock;

import kgu.developers.common.domain.BaseTimeEntity;

import java.lang.reflect.Field;
import java.time.LocalDateTime;

public class TestEntityUtils {
public static void setCreatedAt(BaseTimeEntity entity, LocalDateTime createdAt) {
if (entity == null || createdAt == null) {
throw new IllegalArgumentException("entity와 createdAt은 null이 될 수 없습니다.");
}

try {
Field field = BaseTimeEntity.class.getDeclaredField("createdAt");
field.setAccessible(true);
field.set(entity, createdAt);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("TestEntityUtils에서 createdAt 설정 실패", e);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package post.application;

public class PostServiceTest {
}
Loading