프로젝트 2

코드 굽는 제빵사·2021년 1월 9일
0

문제 : 서비스 계층은 어떻게 테스트 할까?

별도의 오류 검증 로직은 없지만 Dto를 받아서 유저를 생성하는 서비스 계층을 테스트 하려고 했습니다.
그런데 registerStoreUser를 실행하면 내부에 repository메서드를 실행하게 됩니다. 그렇게 된다면 다른 영역의 함수를 호출 해야 검증 할 수 있기 때문에 맞지 않다고 생각 했습니다.

import java.util.UUID;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class StoreUserServiceImpl implements StoreUserService {

    private final StoreUserRepository storeUserRepository;

    @Override
    public UUID registerStoreUser(StoreUserCmdDto dto) {
        StoreUser storeUser = transferDtoToEntity(dto);
        StoreUser saveStoreUser = storeUserRepository.save(storeUser);
        return saveStoreUser.getId();
    }

    public StoreUser transferDtoToEntity(StoreUserCmdDto dto) {
        return new StoreUser(dto.getName(), dto.getAddress());
    }
}

해결 : Mockito를 사용해서 테스트 하자!

이번에는 docs로 잘 찾질 못해서 블로그를 참고해서 테스트 케이스를 작성했다.
Repository를 Mock객체로 변경했고 만약에 별도의 when, thenreturn을 사용하지 않았다면 null
객체를 반환 하면서 NullPointerException이 발생하게 된다. 구현 된 repository를 사용 하지 않고도 서비스 계층을 테스트 할 수 있게 되었다.

@Test
public void 유저등록하기() throws Exception {
        //given
        Address address = createAddress("seoul", "gaepo-dong", "42-42");
        StoreUserCmdDto dto = createCmdDto(address, "nakim");
        StoreUser storeUser = createStoreUser(address, "Hakim");
       //when
        when(storeUserRepository.save(any(StoreUser.class))).thenReturn(storeUser);
        //then
        UUID registerUserId = storeUserService.registerStoreUser(dto);
        assertEquals(registerUserId, storeUser.getId());

private StoreUser createStoreUser(Address address, String name) {
        return new StoreUser(UUID.randomUUID(), name, address);
    }
}

공식 문서
Mockito docs
좋은 테스트를 작성하는 방법

그 외 참고
[SpringBoot] @Mock, @MockBean 차이가 뭘까?
Mockito @Mock @MockBean @Spy @SpyBean 차이점 정리

문제 : update 함수는 어떻게 구성 해야 할까?

모든 정보를 업데이트 할수도 있고 아니면 하나의 항목 또는 두개의 항목을 선택해서 업데이트를 한다면
update함수를 속성마다 setter만들 듯 만들어야 하는지 아니면 한번에 덮어 씌우듯 해야 하는지 몰랐습니다.

@Override
public StoreUserCmdDto updateStoreUserInfo(UUID userId, StoreUserCmdDto dto) {
        StoreUser findStoreUser = storeUserRepository.findById(userId).orElseThrow();
        findStoreUser.update(transferDtoToEntity(dto));
        return transferEntityToDto(findStoreUser);
}

해결 : JPA의 상태 감지를 활용하자!

JPA의 Merge기능은 모든 필드를 변경해버리고, 데이터가 없으면 null로 업데이트 합니다. Merge를 사용하려고 하면 모든 데이터를 유지해야하는데 변경 가능한 부분만 노출하려고 한다면 적합하지 않습니다.
그래서 Transaction이 있는 Service계층에서 변경 감지를 사용하도록 합니다.
업데이트 메서드를 하나로 통합해서 처리 가능하지 않을까 싶습니다.

그래서 저는 아래와 같은 방식으로 진행하려고 합니다.
1. ID와 변경 할 DTO를 전달 받습니다.
2. ID를 통해 영속 상태의 엔티티를 조회합니다.
3. 엔티티의 데이터를 변경합니다.
4. 커밋 시점에 상태감지가 실행되어 업데이트가 됩니다.

@Override
public StoreUserCmdDto updateStoreUserInfo(UUID userId, StoreUserCmdDto dto) {
        StoreUser findStoreUser = storeUserRepository.findById(userId).orElseThrow();
        findStoreUser.update(transferDtoToEntity(dto));
        return transferEntityToDto(findStoreUser);
}

// StoreUser의 update method
public void update(StoreUser dto) {
        super.update(dto.getName(), dto.getAddress());
        this.store = dto.getStore();
}

공식 문서
DataJPA 상태 감지 전략

0개의 댓글