Skip to content
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

[test/#26] 검색 API를 제외한 Diary Controller, Servive 테스트 코드 작성 #30

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f2e7fc8
feat: docs 설정 파일 작성
GaBaljaintheroom Sep 20, 2023
8282976
chore: docs/test 의존성 추가
GaBaljaintheroom Sep 20, 2023
279e288
feat: DatabaseTest 어노테이션 생성
GaBaljaintheroom Sep 20, 2023
d51047f
feat: UserFixture 생성
GaBaljaintheroom Sep 20, 2023
bf1b812
feat: DiaryFixture 생성
GaBaljaintheroom Sep 20, 2023
8358b66
feat: TestProfile 생성
GaBaljaintheroom Sep 20, 2023
884aec3
refactor: DiaryCreateReq 수정
GaBaljaintheroom Sep 20, 2023
7953327
test: 일기 생성을 확인한다.
GaBaljaintheroom Sep 20, 2023
3c93a98
feat: DiaryFixture DELETE_DIARY 객체 생성
GaBaljaintheroom Sep 20, 2023
f18531e
refactor: deleteDiary service 수정
GaBaljaintheroom Sep 20, 2023
7a3e63c
feat: DIARY_CREATE_USER 생성
GaBaljaintheroom Sep 20, 2023
4cd16bc
test: 일기가 PK로 삭제가 되는지 확인한다.
GaBaljaintheroom Sep 20, 2023
e49799f
feat: DiaryFixture InsertDiaries 작성
GaBaljaintheroom Sep 21, 2023
478f921
feat: UserFixture DIARY_FIND_MONTH_USER 작성
GaBaljaintheroom Sep 21, 2023
4f1dcb4
test: 일기들을 달 별로 조회할 수 있다.
GaBaljaintheroom Sep 21, 2023
6348acb
refactor: request DTO 변수명 수정
GaBaljaintheroom Sep 21, 2023
f6a8a39
refactor: pageRequest size 기본 값 수정
GaBaljaintheroom Sep 21, 2023
162778a
feat: DiaryFixture INSERT_SEARCH_DIARIES 작성
GaBaljaintheroom Sep 21, 2023
1f77077
feat: UserFixture DIARY_SEARCH_USER 작성
GaBaljaintheroom Sep 21, 2023
14dcaf0
test: 키워드로 제목,내용을 포함한 일기들을 검색할 수 있다.
GaBaljaintheroom Sep 21, 2023
816e17f
test: 사용자가 작성한 일기가 삭제되도록 수정
GaBaljaintheroom Sep 21, 2023
97aa231
chore: jacoco 관련 설정 추가
GaBaljaintheroom Sep 21, 2023
4a1c444
chore: jacoco 디렉토리 설정 추가
GaBaljaintheroom Sep 21, 2023
50182fd
feat: CustomMockUser 생성
GaBaljaintheroom Sep 28, 2023
0395681
style: 클래스명 수정
GaBaljaintheroom Sep 28, 2023
9f77462
feat: MockApiTest 생성
GaBaljaintheroom Sep 28, 2023
037dc52
feat: DiaryControllerFixture CREATE_REQ 생성
GaBaljaintheroom Sep 28, 2023
aef9cba
test: 일기가 등록되는지 확인한다.
GaBaljaintheroom Sep 28, 2023
f9b648c
test: 사용자가 작성한 일기가 PK로 삭제될 수 있다.
GaBaljaintheroom Sep 28, 2023
3d51d69
test: 사용자가 작성한 일기를 달별로 조회할 수 있다.
GaBaljaintheroom Sep 28, 2023
bad42f2
docs: https://docs.api.com 으로 변경
GaBaljaintheroom Sep 28, 2023
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
86 changes: 83 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
plugins {
id 'java'
id 'jacoco'
id 'org.springframework.boot' version '2.7.14-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2"
}

group = 'com.aiary'
Expand All @@ -11,9 +13,55 @@ java {
sourceCompatibility = '11'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
jacoco {
toolVersion = '0.8.5'
}

// 테스트 태스크에 Jacoco 통합
test{
finalizedBy 'jacocoTestReport'
}

// 테스트 작업이 완료된 후 'jacocoTestReport' 작업을 실행하도록 설정
jacocoTestReport {
reports {
xml.required = false
csv.required = false
html.required = true
}
finalizedBy jacocoTestCoverageVerification
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
'**/dto/**',
"**/*Application*",
"**/global/**",
"**/config/**",
"**/exceptions/**"
])
}))
}
}

// Jacoco 코드 커버리지 검증 설정
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
element = 'CLASS'

limit {
counter = 'METHOD'
value = 'COVEREDRATIO'
minimum = 0.00
}

limit {
counter = 'INSTRUCTION' // 조건, 반복문
value = 'COVEREDRATIO'
minimum = 0.00
}
}
}
}

Expand All @@ -40,10 +88,42 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'

// test
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:4.8.0'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}

tasks.named('test') {
useJUnitPlatform()
}

// 변수 선언
// snippetsDir 변수는 생성된 스니펫 파일을 저장할 디렉토리를 지정합니다. 스니펫 파일은 API 엔드포인트에서 생성된 예제 요청 및 응답 정보를 포함합니다.
ext {
snippetsDir = file('build/generated-snippets')
}

// bootjar과 관련된 설정이며, 스니펫을 이용해 문서 작성 후,
// build - docs - asciidoc 하위에 생기는 html 파일을 static/docs로 복사해줍니다.
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}/html5")
into 'static/docs'
}

asciidoctor {
inputs.dir snippetsDir
dependsOn test
}

configurations {
asciidoctorExtensions
compileOnly {
extendsFrom annotationProcessor
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public ResponseEntity<ResultResponse> getMonthlyDiary(@AuthenticationPrincipal U
@GetMapping("/search")
public ResponseEntity<ResultResponse> searchDiariesByKeyWord(@AuthenticationPrincipal UserDetail user,
@RequestParam(defaultValue = "0", required = false) int page,
@RequestParam(defaultValue = "10", required = false)int size,
@RequestParam(defaultValue = "40", required = false)int size,
@RequestParam("diary_date") String diaryDate,
@RequestParam String keyword){
PageRequest pageRequest = PageRequest.of(page, size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ public MonthlyDiaryRes toMonthlyDiaryList(List<Diary> monthlyDiaries){
}

public SearchDiariesRes toDiarySlice(Slice<Diary> diaries){
List<DiaryRes> diaryInfos = diaries.stream()
List<DiaryRes> diaryRes = diaries.stream()
.map(this::toEntity)
.collect(Collectors.toList());

return SearchDiariesRes.builder()
.diaryInfos(diaryInfos)
.diaryRes(diaryRes)
.curPageNumber(diaries.getNumber())
.hasNext(diaries.hasNext())
.hasPrevious(diaries.hasPrevious())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import java.time.LocalDate;

@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class DiaryCreateReq {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class SearchDiariesRes {

private List<DiaryRes> diaryInfos;
private List<DiaryRes> diaryRes;
private int curPageNumber;
private boolean hasNext;
private boolean hasPrevious;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.aiary.aiary.domain.user.entity.User;
import com.aiary.aiary.domain.user.entity.UserDetail;
import com.aiary.aiary.domain.user.exception.UnAuthorizedAccessException;
import com.aiary.aiary.domain.user.service.UserService;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
Expand All @@ -27,7 +26,6 @@
public class DiaryService {

private static final String FIRST_DAY = "-01";
private final UserService userService;
private final DiaryRepository diaryRepository;
private final DiaryMapper diaryMapper;

Expand All @@ -37,9 +35,9 @@ public void createDiary(UserDetail userDetail, DiaryCreateReq diaryCreateReq, St
}

@Transactional
public void deleteDiary(UserDetail userDetail, Long diaryId){
public void deleteDiary(UserDetail userDetail, Long diaryId){
Diary deleteDiary = findDiaryWithUser(diaryId);
User user = userService.findUserById(userDetail.getUserId());
User user = userDetail.getUser();

if (!Objects.equals(user.getId(), deleteDiary.getUser().getId()))
throw new UnAuthorizedAccessException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.aiary.aiary.domain.diary.controller;

import com.aiary.aiary.domain.diary.dto.request.DiaryCreateReq;
import com.aiary.aiary.domain.diary.dto.response.DiaryRes;
import com.aiary.aiary.domain.diary.dto.response.MonthlyDiaryRes;
import com.aiary.aiary.domain.diary.service.DiaryService;
import com.aiary.aiary.domain.user.entity.UserDetail;
import com.aiary.aiary.global.s3.service.S3UploadService;
import com.aiary.aiary.support.fixture.DiaryControllerFixture;
import com.aiary.aiary.support.utils.MockApiTest;
import com.aiary.aiary.support.utils.WithCustomMockUser;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.ResultActions;

import java.nio.charset.StandardCharsets;
import java.util.List;

import static com.aiary.aiary.support.docs.ApiDocumentUtils.getDocumentRequest;
import static com.aiary.aiary.support.docs.ApiDocumentUtils.getDocumentResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@WebMvcTest(DiaryController.class)
@ExtendWith(RestDocumentationExtension.class)
@DisplayName("Diary 컨트롤러에 ")
class DiaryControllerTest extends MockApiTest {

@MockBean
private DiaryService diaryService;
@MockBean
private S3UploadService s3UploadService;

@Test
@WithCustomMockUser
@DisplayName("일기가 등록될 수 있다.")
void createDiary() throws Exception {
//given
MockMultipartFile file = new MockMultipartFile(
"file", // 파라미터 이름
"file_name", // 파일 이름
"image/jpeg",// 파일 타입
"test file".getBytes(StandardCharsets.UTF_8) // 파일 내용
);
given(s3UploadService.saveFile(any(), any())).willReturn(String.valueOf(file));

DiaryCreateReq createReq = DiaryControllerFixture.CREATE_REQ;
String jsonByCreateReq = objectMapper.writeValueAsString(createReq);
MockMultipartFile request = new MockMultipartFile(
"createRequest",
"createRequest",
"application/json",
jsonByCreateReq.getBytes(StandardCharsets.UTF_8)
);
String token = "accessToken";
diaryService.createDiary(any(), any(), any());

//when
ResultActions perform =
mockMvc.perform(
multipart("/diaries")
.file(file)
.file(request)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
);

// then
perform.andExpect(status().isOk());

// docs
perform.andDo(print())
.andDo(document("register diary",
getDocumentRequest(),
getDocumentResponse()));

}

@Test
@DisplayName("사용자가 작성한 일기가 PK로 삭제될 수 있다.")
void deleteDiary() throws Exception {
//given
doNothing().when(diaryService).deleteDiary(any(UserDetail.class), any(Long.class));

//when
ResultActions perform =
mockMvc.perform(
delete("/diaries/{diaryId}", 1L));
//then
perform.andExpect(status().isOk());

//docs
perform.andDo(print())
.andDo(document("delete diary",
getDocumentRequest(),
getDocumentResponse()));
}

@Test
@DisplayName("사용자가 작성한 일기를 달별로 조회할 수 있다.")
void getMonthlyDiary() throws Exception {
// given
String diaryDate = "2023-09";
List<DiaryRes> expectedDiaries = DiaryControllerFixture.MONTHLY_DIARIES();
MonthlyDiaryRes monthlyDiaryRes = MonthlyDiaryRes.builder()
.monthlyDiaryRes(expectedDiaries)
.build();
given(diaryService.findMonthlyDiaryByDate(any(UserDetail.class), eq(diaryDate))).willReturn(monthlyDiaryRes);

// when
ResultActions perform = mockMvc.perform(
get("/diaries")
.param("diary_date", diaryDate)
.contentType(MediaType.APPLICATION_JSON));

// then
perform.andExpect(status().isOk())
.andDo(print())
.andDo(document("get monthlyDiaries",
getDocumentRequest(),
getDocumentResponse(),
responseFields(
fieldWithPath("code").description("상태 코드"),
fieldWithPath("message").description("응답 메시지"),
fieldWithPath("data").description("응답 데이터").type(JsonFieldType.OBJECT).optional(),
fieldWithPath("data.monthly_diary_info").description("월별 일기 정보").type(JsonFieldType.ARRAY).optional(),
fieldWithPath("data.monthly_diary_info[].diary_id").type(JsonFieldType.NUMBER).description("일기 ID").optional(),
fieldWithPath("data.monthly_diary_info[].title").type(JsonFieldType.STRING).description("일기 제목").optional(),
fieldWithPath("data.monthly_diary_info[].weather").type(JsonFieldType.STRING).description("일기 날씨").optional(),
fieldWithPath("data.monthly_diary_info[].emoji").type(JsonFieldType.STRING).description("이모지").optional(),
fieldWithPath("data.monthly_diary_info[].contents").type(JsonFieldType.STRING).description("일기 내용").optional(),
fieldWithPath("data.monthly_diary_info[].diary_date").type(JsonFieldType.STRING).description("일기 날짜").optional(),
fieldWithPath("data.monthly_diary_info[].drawing_url").type(JsonFieldType.STRING).description("일기 그림 URL").optional())
));
}

}
Loading
Loading