STOVE DEVCAMP 3기/알림 서비스
[알림 서비스] 테스트 코드 작성
sw_develop
2023. 3. 10. 04:20
테스트 코드 작성 규칙을 세워 알림 서비스 구현 시 단위 테스트 코드 작성을 수행함
📌 어느 부분을 작성했는지
- 알림 서비스 구현 시 클린 아키텍처를 도입하여 아래와 같이 구성함 - 참고) 클린 아키텍처 도입
- 따라서 패키지 구조는 다음과 같음
- 헥사고날 아키텍처를 기반으로 비즈니스 로직을 포함한 내부 영역과 외부 영역을 분리하였고, 이를 기반으로 단위 테스트 코드 작성 부분은 아래의 3가지로 설정함
- adapter/in 패키지의 Web Adatper
- adapter/out 패키지의 Persistence Adapter
- application 패키지의 Service
📌 어떤 방식으로 작성했는지
공통
- Given-When-Then 패턴 사용
- 테스트 코드를 작성하는 표현 방식 중 하나임
- Given : 테스트 하기 위해 기본적으로 세팅하는 값
- When : 테스트 하기 위한 조건 지정
- Then : 테스트 하기 위한 행위가 우리가 예상하는대로 동작하는지 검증하는 행동 / 절차
- 성공, 실패 테스트 케이스 작성
adapter/in 패키지의 Web Adatper
- @WebMvcTest 사용
- Application Context를 완전히 시작 시키지 않고, Web Layer에 대한 단위 테스트 수행을 위해 사용함
- HTTP Request의 Param, Request Body에 대한 유효성 검사 수행
작성 예시 - 알림 수신 디바이스 등록 API 호출
@WebMvcTest(DeviceController.class)
public class DeviceControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
RegisterDeviceUseCase registerDeviceUseCase;
@MockBean
DeleteDeviceUseCase deleteDeviceUseCase;
@MockBean
UpdateNotificationSettingUseCase updateNotificationSettingUseCase;
@MockBean
GetNotificationSettingUseCase getNotificationSettingUseCase;
@Test
@DisplayName("디바이스 등록 성공")
void registerDeviceSuccess() throws Exception {
//given
String requestBody = """
{
"token": "clutter-token"
}
""";
//when, then
mockMvc.perform(post("/api/notification/device")
.header("userId", "clutter-user-id")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("001"))
.andExpect(jsonPath("$.message").value("성공했습니다."))
.andExpect(jsonPath("$.data").isEmpty());
}
@Test
@DisplayName("디바이스 토큰 값이 null인 경우 예외 반환")
void throwExceptionIfTokenNull() throws Exception {
//given
String requestBody = """
{
"token": null
}
""";
//when, then
mockMvc.perform(post("/api/notification/device")
.header("userId", "clutter-user-id")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.code").value("-401"))
.andExpect(jsonPath("$.message").value("디바이스 토큰 값은 필수입니다."));
}
}
|
- @MockBean으로 의존성 주입을 받아 테스트 진행함
adapter/out 패키지의 Persistence Adapter
- @DataMongoTest
- MongoDB의 컴포넌트들을 중점적으로 테스트 가능
- @Document를 스캔하여 MongoTemplate 생성
- 실제 데이터베이스와 통신하여 로직이 예상과 동일하게 동작하는지 검증 수행
작성 예시 - 디바이스 조회 로직
@Disabled
@ActiveProfiles("test")
@DataMongoTest(excludeAutoConfiguration = MongoDBConfig.class)
@Import({DevicePersistenceAdapter.class,
DeviceMapper.class,
TokenMapper.class,
DeviceMongoTemplate.class,
MongoDBTestConfig.class})
class DevicePersistenceAdapterTest {
@Autowired
DevicePersistenceAdapter devicePersistenceAdapter;
@Autowired
DeviceRepository deviceRepository;
@Autowired
DeviceMongoTemplate deviceMongoTemplate;
@AfterEach
void cleanUp() {
deviceRepository.deleteAll();
}
@Test
@DisplayName("디바이스가 존재하는 경우 객체를 반환함")
void findByUserIdAndTokenExist() {
//given
TokenDocument tokenDocument = TokenDocument.builder()
.value("clutter-token")
.timestamp(LocalDateTime.now()).build();
DeviceDocument deviceDocument = DeviceDocument.builder()
.userId("clutter-user")
.pushNotificationOpt(true)
.tokenDocument(tokenDocument).build();
deviceRepository.save(deviceDocument);
//when
Device device = devicePersistenceAdapter.findByUserIdAndToken("clutter-user", "clutter-token");
//then
assertThat(device.getToken().getValue()).isEqualTo("clutter-token");
assertThat(device.getUserId()).isEqualTo("clutter-user");
}
@Test
@DisplayName("디바이스가 존재하지 않는 경우 null을 반환함")
void findByUserIdAndTokenNotExist() {
//when
Device device = devicePersistenceAdapter.findByUserIdAndToken("clutter-user", "clutter-token");
//then
assertThat(device).isNull();
}
...
}
|
application 패키지의 Service
- JUnit
- 내가 작성한 로직이 예상과 동일하게 동작하는지 검증 수행
작성 예시
- 알림 목록 조회 시 사용되는 커서 페이지네이션 로직에 대한 검증 수행함
- 분기문에 대한 테스트 케이스를 상세히 작성함
public class GetNotificationServiceTest {
private final GetNotificationPort getNotificationPort = Mockito.mock(GetNotificationPort.class);
private final GetNotificationService getNotificationService = new GetNotificationService(getNotificationPort);
@Test
@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1)보다 작으면 hasNextPage=false")
void hasNextPageFalseIfListSizeLtPageSize() {
//given
NotificationListQueryReq listQueryReq = NotificationListQueryReq.builder()
.userId("clutter-user-id")
.direction(true)
.nextIndex(null)
.pageSize(20).build();
List<Notification> notifications = createNotificationList(20);
given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);
//when
NotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);
//then
assertThat(listQueryRes.getNotifications().size()).isEqualTo(20);
assertThat(listQueryRes.getNextIndex()).isEqualTo("-1");
assertThat(listQueryRes.isHasNextPage()).isFalse();
}
@Test
@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1) 이상이고 아래 방향 스크롤이면, nextIndex=리스트의 마지막 원소의 id이고 hasNextPage=true")
void hasNextPageTrueIfListSizeGtePageSizeWithDownScroll() {
//given
NotificationListQueryReq listQueryReq = NotificationListQueryReq.builder()
.userId("clutter-user-id")
.direction(true)
.nextIndex(null)
.pageSize(20).build();
List<Notification> notifications = createNotificationList(21);
given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);
//when
NotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);
//then
assertThat(listQueryRes.getNotifications().size()).isEqualTo(20);
assertThat(listQueryRes.getNextIndex()).isEqualTo("notification-id1");
assertThat(listQueryRes.isHasNextPage()).isTrue();
}
@Test
@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1) 이상이고 위 방향 스크롤이면, nextIndex=리스트의 첫번째 원소의 id이고 hasNextPage=true")
void hasNextPageTrueIfListSizeGtePageSizeWithUpScroll() {
//given
NotificationListQueryReq listQueryReq = NotificationListQueryReq.builder()
.userId("clutter-user-id")
.direction(false)
.nextIndex("notification-id1")
.pageSize(20).build();
List<Notification> notifications = createNotificationList(21);
given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);
//when
NotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);
//then
assertThat(listQueryRes.getNotifications().size()).isEqualTo(20);
assertThat(listQueryRes.getNextIndex()).isEqualTo("notification-id21");
assertThat(listQueryRes.isHasNextPage()).isTrue();
}
...
}
|