[F-Lab 모각코 챌린지 35일차]

부추·2023년 7월 5일
0

F-Lab 모각코 챌린지

목록 보기
35/66
post-thumbnail

1. 프론트 페이지

단축 URL에 사용할 프론트 페이지를 만들었다! 폼 코드는 아래와 같다.

<form id="createForm">
    <label for="originalUrl">원본 URL : </label>
    <input type="text" 
           id="originalUrl" 
           name="originalUrl" 
           placeholder="http://example.com">
    <button type="submit">생성</button>
</form>
<p id="generatedShortenUrl"></p>

originalUrl name으로 input을 받을 것이다. 생성 버튼을 눌러 submit 이벤트가 발생하면, 지정한 url로 POST 요청을 하도록 구성했다. 동일한 페이지의 scrpit 태그에 해당 역할을 수행하는 javascript 코드를 작성했다.

코드가 좀 길다 ㅜㅜ fetch API를 사용했다. 예시론 URL 생성 폼만 작성했지만 조회 폼도 전반적인 기능은 같다! method와 BODY 존재 유무만 다를 뿐..

document.addEventListener('DOMContentLoaded', function() {
        const createForm = document.getElementById('createForm');

        createForm.addEventListener('submit', function(e) {
            e.preventDefault();
          
            const formData = new FormData(createForm);
            var requestData = {};
            formData.forEach(function(value, key) {
                requestData[key] = value;
            });
          
            fetch('/url', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(requestData)
            })
            .then(function(response) {
                if (response.ok) {
                    return response.json();
                } else {
                    return response.json().then(function(err) {
                        throw new Error(err.message);
                    });
                }
            })
            .then(function(data) {
                const generatedDom = document.getElementById('generatedShortenUrl');
                generatedDom.innerHTML = "생성된 단축 URL : " + data.shortenUrl;
            })
            .catch(function(error) {
                const generatedDom = document.getElementById('generatedShortenUrl');
                generatedDom.innerHTML = "잘못된 URL입니다.";
            });
        });
    });
}
  • createForm DOM에 addEventListener을 사용해 제출 이벤트를 핸들링하고 있다.
  • requestData에 폼으로 제출된 originalUrl데이터를 담고, fetch API를 통해 /url path로 POST 요청을 날렸다.
  • API 서버에서 200 ok를 응답으로 반환하면 generatedShortenUrl를 가진 p태그에 응답으로 온 datashortenUrl값이 들어가게 되고, 그렇지 않으면 "잘못된 URL입니다"라는 값이 들어가게 구성했다.

원본 URL input 항목에 https://youtube.com을 입력하고 "생성"버튼을 클릭했다.
생성된 단축 URL이 뜬다. 이제 localhost:8080/g8mixKb에 접속해보면?
굳굳! (근데 유튜브 메인 너무씹덕아니에요?)

이제 단축 URL 조회 폼에 방금의 단축된 URL을 기입해보았다.
요청 횟수도 잘 표시된다!

유효하지 않은 URL을 등록하려 하거나, 존재하지 않는 URL을 조회하려고 하면 오류 문구가 나온다.



2. 테스트

1) 컨트롤러 유닛 테스트

MockMvc@MockBean을 이용해서 컨트롤러 테스트를 한다.

test 디렉토리 아래, controller 패키지에 다음과 같은 Test 클래스를 추가해줬다. @ExtendWith을 통해 mockito를 이용한 단위 테스트를 할 것임을 명시했고, mockMvc를 통해 가짜 request를 날릴 준비를 마쳤다. ObjectMapper은 요청으로 날릴 body객체를 위함이고, UrlService 서비스 객체를 가짜 객체로 만들어줄 @MockBean 어노테이션을 사용했다.

@WebMvcTest(UrlRestController.class)
@ExtendWith(MockitoExtension.class)
public class UrlRestControllerTest {
    @MockBean
    private UrlService urlService;

    @Autowired // 자동으로 mockMvc 빌드
    MockMvc mockMvc;

    @Autowired
    ObjectMapper objectMapper;
}

일단 valid한 POST 요청에 대해, 단축 URL 생성이 잘 된 응답이 도착하는지 확인하는 코드이다.

@DisplayName("단축 URL 생성 확인")
@Test
public void createShortenUrl() throws Exception {
    Mockito.when(urlService.shortenUrl(Mockito.anyString()))
            .thenReturn(ShortenUrl.builder()
                    .originalUrl("http://test.com")
                    .shortenUrl("test123")
                    .build());

    mockMvc.perform(
        MockMvcRequestBuilders
                .post("/url")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(objectMapper.writeValueAsString(
                            new CreateShortenUrlDto.Request(Mockito.anyString()))))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$.shortenUrl").exists());
}
  • when().thenReturn() 메소드를 통해 가짜 객체 urlService의 동작을 지정했다. 서비스 빈의 shortenUrl()이 호출되면 특정 ShortenUrl 도메인 객체가 반환될 것이다.
  • MockMvcRequestBuilders를 통해 POST 요청을 날렸다. body로 지정된 json 값을 줬을 때, 1) 응답 코드가 200인지, 2) 단축된 URL 결과물 항목인 shortenUrl이 존재하는지 andExpect()를 통해 확인했다.

위대하신 멘토님 가로되, 테스트에서 정말 중요한 것은 라이브러리의 사용법 그 자체보다 유닛 테스트를 하고자 하는 클래스의 어떤 기능을 테스트 할 것인지, 그러기 위해서 mock으로 어떤 객체를 두고 그 객체의 mocking 행동을 어떻게 구성할 것인지 고민하는 것이 더 중요하다고 .. 맞는 말씀이다.

실제 단축된 URL로 리다이렉트가 이뤄지는지, 즉 상태코드가 300번대이고 헤더의 Location 항목이 존재하는지 역시 테스트해보았다.

@DisplayName("리다이렉트 테스트")
@Test
public void createWithInvalidRequest() throws Exception {
    Mockito.when(urlService.getOriginalUrl(Mockito.anyString()))
            .thenReturn("http://test.com");

    mockMvc.perform(
        MockMvcRequestBuilders.get("/{shortenUrl}","test123"))
            .andExpect(MockMvcResultMatchers.status().is3xxRedirection())
            .andExpect(MockMvcResultMatchers.header().exists("Location"))
            .andDo(MockMvcResultHandlers.print());
}
  • mock 객체인 urlServicegetOriginalUrl()이 호출되었을 때 반환할 가짜 원본 url을 동작시켰다.
  • andExpect()를 통해 리다이렉트 동작을 위한 상태코드와 헤더 항목이 존재함을 확인하고,
  • andDo()를 통해 HTTP 메세지 항목들을 테스트하며 확인할 수 있도록 했다.

2) 서비스 유닛 테스트

역시 서비스 테스트를 위해 기본 셋팅을 했다.

@ExtendWith(MockitoExtension.class)
public class UrlServiceUnitTest {
    @InjectMocks
    private UrlService urlService;

    @Mock
    private UrlRepository urlRepository;
}
  • @InjectMocks를 통해 urlService 빈에 mock 객체들을 주입할 것이라고 표지했다.
  • urlRepository는 서비스 객체에 주입될 mock repository이다.

서비스 유닛 테스트에선 서비스 로직이 잘 동작하는지 확인한다!

@DisplayName("형식에 맞지 않는 URL 요청일 경우 InvalidUrlException")
@Test
public void requestWithInvalidUrl() {
    String invalidUrl = "test.invalid";

    Assertions.assertThrows(InvalidUrlException.class,() -> {
        urlService.shortenUrl(invalidUrl);
    });
}

@DisplayName("존재하지 않는 URL 조회 요청일 경우 UrlNotFoundException")
@Test
public void requestWithNotExistUrl() {
    Mockito.when(urlRepository.findByShortenUrl(Mockito.anyString()))
            .thenReturn(Optional.empty());

    Assertions.assertThrows(UrlNotFoundException.class,() -> {
        urlService.getByShortenUrl("http://test.com");
    });
}

여기선 요청된 URL에 대한 fail validation과 repository에서 null을 반환했을 때 예외가 잘 던져지는지에 대해 테스트했다.

마지막으로 domain 영역의 기능까지 이용하는 통합 테스트를 했다.

@SpringBootTest
public class UrlServiceTest {
    @Autowired
    private UrlService urlService;

    @DisplayName("단축된 URL이 7자리가 맞는지 확인")
    @Test
    public void shortenUrlLengthTest() {
        String testUrl = "http://test.com";
        Assertions.assertEquals(7,
                urlService.shortenUrl(testUrl).getShortenUrl().length());
    }

    @DisplayName("저장한 shortenUrl을 다시 불러왔을 때 같은지 확인")
    @Test
    public void testSavedUrl() {
        String testUrl = "http://test.com";
        String savedShortenUrl = urlService.shortenUrl(testUrl).getShortenUrl();

        Assertions.assertEquals(testUrl, urlService.getOriginalUrl(savedShortenUrl));
    }
}

urlService.sortenUrl()에서 URL을 생성하는 로직, 생성된 URL을 DB에 저장하는 로직까지 한꺼번에 수행하고 있어서 통합 테스트로만 확인이 가능했따 ~_~... 더 좋은 방법이 있었을까?

profile
부추튀김인지 부추전일지 모를 정도로 빠싹한 부추전을 먹을래

0개의 댓글