728x90
이번 포스팅은 Service를 구현하고 그에 대한 테스트 코드를 작성할 것이다.
우선 Service를 구현하기 전에 계층 간 데이터 전송을 하기 위해 먼저 DTO를 구현한다.
🧷DTO
@Getter
@NoArgsConstructor
public static class SaveRequest{
private String categoryName;
private String parentCategory;
private Map<String,CategoryInfo> subCategory;
@Builder
public SaveRequest(String categoryName, String parentCategory, Map<String, CategoryInfo> subCategory) {
this.categoryName = categoryName;
this.parentCategory = parentCategory;
this.subCategory = subCategory;
}
public Category toEntity(){
Category category = Category.builder()
.categoryName(categoryName)
.build();
return category;
}
}
- childCategory를 get 메서드를 통해 json형식으로 데이터를 넘길 때 , Map으로 넘겨준다.
🗞Service
createCategory()
@Transactional
public void createCategory(SaveRequest request){
Boolean existByName = categoryRepository.existsByCategoryName(request.getCategoryName());
if (existByName){
throw new IllegalArgumentException("카테고리 중복");
}
if (request.getParentCategory() == null){
SaveRequest rootCategory = SaveRequest.builder()
.categoryName(request.getCategoryName())
.parentCategory(null)
.build();
categoryRepository.save(rootCategory.toEntity());
}else{
String parentCategory1 = request.getParentCategory();
Category parentCategory = categoryRepository.findByCategoryName(parentCategory1)
.orElseThrow();
Category category = Category.builder()
.categoryName(request.getCategoryName())
.parentCategory(parentCategory)
.build();
parentCategory.getChildCategory().add(request.toEntity());
categoryRepository.save(category);
}
}
- 우선 첫번째로 카테고리 이름이 중복되는지 검증을 해준다. 만약 같은 카테고리 이름이 존재한다면 예외를 던져준다.
- 만약 부모 카테고리가 없는 경우는 null값을 넣어준다.(추후에 다른 Default값이 들어가게 구현 예정)
- 부모 카테고리가 있다면 검색을 한 후 자식 카테고리에 현재 카테고리를 넣어준다.
다음은 조회 로직이다.
🗓GET
@Getter
@NoArgsConstructor
@Builder
public static class CategoryResponse{
private Long id;
private String categoryName;
private Map<String,CategoryInfo> subCategory=new HashMap<>();
public CategoryResponse(Long id, String categoryName, Map<String, CategoryInfo> subCategory) {
this.id = id;
this.categoryName = categoryName;
this.subCategory = subCategory;
}
}
조회할 데이터를 받아올 DTO를 생성한다.
public CategoryDto.CategoryResponse toCategoryResponse(){
return CategoryDto.CategoryResponse.builder()
.id(this.id)
.categoryName(this.categoryName)
.subCategory(
this.childCategory.stream().collect(Collectors.toMap(
Category::getCategoryName, CategoryDto.CategoryInfo::new
))
)
.build();
}
다음 Entity클래스에 조회시 받아올 데이터에 대한 값을 스펙을 설정해준다.
@Transactional(readOnly = true)
public CategoryResponse getCategoryById(Long id){
CategoryResponse response = categoryRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("찾는 카테고리가 존재하지 않음"))
.toCategoryResponse();
return response;
}
서비스 계층에 메서드를 생성하고 아이디로 저장소에서 카테고리를 찾고 없으면 예외를 던져준다.
(추후 이름으로 조회하는 로직도 구현 예정)
다음으로 삭제와 수정 로직인데 이 로직들은 딱히 복잡한 것이 없다.
🗓Delete
@Transactional
public void deleteCategory(Long id){
Category category = categoryRepository.findById(id)
.orElseThrow(IllegalArgumentException::new);
categoryRepository.deleteById(id);
}
- Id로 카테고리를 검색하고 만약 카테고리가 존재하지 않으면 예외를 던진다.
- 그리고 자식 카테고리가 존재하는 경우 삭제할 수 없도록 예외를 던져준다.
🗓Update
public void update(SaveRequest request){
this.categoryName = request.getCategoryName();
this.parentCategory = request.toEntity().getParentCategory();
}
entity 클래스에 update 메서드를 생성한다.
@Transactional
public void updateCategory(Long id,SaveRequest request){
Category category = categoryRepository.findById(id).orElseThrow(IllegalArgumentException::new);
category.update(request);
}
우선 수정 로직은 저장과 비슷하지만 categoryRepository에서 id로 카테고리를 찾은 후 업데이트해준다.
🗓TestCode
- 진짜 테스트 코드는 서비스가 커지고 로직이 복잡할수록 중요하다.
- 내가 개발했지만 서비스가 커지면 뭐가 뭔지 헷갈릴 때가 있다. 그러므로 핵심 로직을 통과하기 위한 데이터를 생성하여 비즈니스 로직을 디버깅하고, 문제를 발견하는 작업이 중요하다.
이제 Service에 대한 테스트 코드를 작성해보자
우선 기본 설정과 들어갈 데이터 값을 설정해준다.
@ExtendWith(MockitoExtension.class)
class CategoryServiceTest {
@InjectMocks
CategoryService categoryService;
@Mock
CategoryRepository categoryRepository;
public CategoryDto.SaveRequest request1(){
return CategoryDto.SaveRequest.builder()
.categoryName("category1")
.parentCategory(null)
.build();
}
}
- @RunWith(MockitoExtension.class) : JUnit5부터 @ExtendWith(MockitoExtension.class)를 사용한다.
- @InjectMocks : @Mock이 붙은 목객체를 @InjectMocks이 붙은 객체에 주입시킬 수 있다.
@Mock : mock 객체를 생성해준다. - assertThat으로 값을 비교해준다.
⛏save_category()
@Test
@DisplayName("카테고리 생성")
void save_category()throws Exception{
//given
CategoryDto.SaveRequest request1 = request1();
//when
when(categoryRepository.existsByCategoryName(request1.getCategoryName()))
.thenReturn(false);
categoryService.createCategory(request1);
//then
Assertions.assertThat(request1.getCategoryName()).isEqualTo(request1().getCategoryName());
Assertions.assertThat(request1.getParentCategory()).isEqualTo(null);
}
⛏update_category()
@Test
@DisplayName("카테고리 수정")
void update_category()throws Exception{
//given
Category category = request1().toEntity();
Long id = category.getId();
//when
when(categoryRepository.findById(id)).thenReturn(Optional.of(category));
categoryService.updateCategory(id,request2());
//then
Assertions.assertThat(category.getCategoryName()).isEqualTo(request2().getCategoryName());
}
⛏delete_category()
@Test
@DisplayName("카테고리 삭제")
void delete_category()throws Exception{
//given
Category category = request1().toEntity();
Long id = category.getId();
//when
when(categoryRepository.findById(id)).thenReturn(Optional.of(category));
categoryService.deleteCategory(id);
//then
verify(categoryRepository,atLeastOnce()).deleteById(id);
}
테스트 코드를 실행하면 정상적으로 통과된다.

😸예제코드
https://github.com/ryudongjae/blog-ex
GitHub - ryudongjae/blog-ex: 📁블로그 예제 코드
📁블로그 예제 코드 . Contribute to ryudongjae/blog-ex development by creating an account on GitHub.
github.com
728x90
'Dev > Spring' 카테고리의 다른 글
| [Spring] Maven과 Gradle (0) | 2022.01.06 |
|---|---|
| [Spring] JPA+Spring으로 카테고리 로직 구현 - 3 (0) | 2021.12.10 |
| [Spring] JPA+Spring으로 카테고리 로직 구현 - 1 (0) | 2021.12.10 |
| [Spring] 어노테이션으로 로그인 여부 확인(Interceptor) (0) | 2021.09.21 |
| [Spring] AOP(Aspect Oriented Programming) (0) | 2021.09.10 |