edu-pi / Backend-User-Server

0 stars 0 forks source link

[Feat] 로그인 기능 구현 #7

Closed ujkkk closed 3 months ago

ujkkk commented 3 months ago

📝 Description

무엇을?

❗️Todo

ETC

기타사항

SongGwanSeok commented 3 months ago

UserServer에서 login을 구현할 때 email, password를 통해 DbServer에서 Member 객체를 불러와야 하는 필요가 있었다. 두개의 서버는 분리되어 있기 때문에 Http통신을 통해 값을 받아와야 헀는데, spring에서 Http 통신을 지원하는 방식에 대해 고민하게 되었다.

RestTemplate vs WebClient vs RestClient

대표적으로 3가지 방식을 많이 사용하는 것으로 확인했다.

  1. RestTemplate
    • 이 방식은 Spring 3.0버전부터 간편하게 HTTP 통신을 지원하는 내장 객체이다.
    • 하지만, 아래 나온 WebClient가 등장하게 되면서 유지관리 대상이 되었다.
  2. WebClient
    • Spring 5.0 버전에 WebFlux와 함께 새로 내놓은 인터페이스이다.
    • webflux라는 의존성에 들어가있기 때문에 의존성을 따로 추가해주어야 한다.
    • Spring MVC에서 사용하기 불편하다. block을 사용해 해결할 수 있지만 안티패턴이라고 한다.
  3. RestClient
    • WebClient와 거의 비슷한 형태로 사용할 수 있다.
    • fluent API를 그대로 사용해서 RestTemplate의 인프라와 함께 사용할 수 있다.

RestClient 와 Http Interface를 활용한 외부 API 출력

Http Interface란? HTTP 서비스를 자바 인터페이스로 정의하고, HTTP 교환 메소드를 사용할 수 있는 기능을 제공합니다. 그럼 이렇게 정의된 인터페이스를 구현하는 프록시를 생성할 수 있는데요, 이 프록시가 실제로 HTTP 교환을 수행하게 됩니다.

Http Interface를 활용하면 RestTemplate, RestClient, WebClient와 같은 클라이언트에 의존하지 않고 쉽게 클라이언트를 변경할 수 있다!

RestClient를 활용한 Http 통신 test

RestClient를 사용하는 경우 test코드를 어떻게 작성해야 하나...

처음에는 아래 코드처럼 restClient를 mocking하고 사용하는 memberRepository에 주입해주는 방식을 생각했다. 하지만 RestClient를 MemberRepository에서 직접 create()하기 때문에 적절하지 않고, when() 내부에서 restclient.post()가 null을 반환하는 문제가 발생했다.

@ExtendWith(MockitoExtension.class)  
class MemberRepositoryTest {  

    @InjectMocks  
    private MemberRepository memberRepository;  

    @Mock  
    private RestClient restClient;  

    @Test  
    @DisplayName("Test findMemberByEmailAndPassword")  
    void testFindMemberByEmailAndPassword() {  
        Member expectedMember = Member.builder()  
            .email("test@example.com")  
            .name("Test User")  
            .role("ROLE_USER")  
            .build();  

        MemberLoginRequest memberLoginRequest = MemberLoginRequest.builder()  
            .email("test@example.com")  
            .password("password")  
            .build();  

        when(restClient.post()  
            .uri("http://localhost:8081/api/v1/member/login")  
            .contentType(MediaType.APPLICATION_JSON)  
            .body(memberLoginRequest)  
            .retrieve()  
            .body(Member.class))  
            .thenReturn(expectedMember);  

        Member result = memberRepository.findMemberByEmailAndPassword(memberLoginRequest);  

        Assertions.assertThat(result).isEqualTo(expectedMember);  
    }  
}

이런 문제를 해결하기 위해 구글링을 통해 MockWebServer를 사용해 mocking하는 방법을 찾았다.

MockWebServer

MockWebServer란 HttpRequest를 받아서 response를 반환하도록 도와주는 작은 웹서버를 제공해준다. 실제로 Spring Team에서도 이런 방식을 권장한다고 한다.

참고 사이트 WebClinet 외부 호출 API를 MockWebServer 사용해서 테스트하기

@SpringBootTest  
@Import(MemberApiRestClientConfig.class)  
class MemberApiClientTest {  

    @Autowired  
    private ObjectMapper mapper;  

    private MockWebServer mockWebServer;  
    private MemberApiClient memberApiClient;  

    @BeforeEach  
    void setUp() throws IOException {  
        mockWebServer = new MockWebServer();  
        mockWebServer.start(8081);  

        // memberApiClient에 mockWebServer를 적용해 테스트  
        MemberApiRestClientConfig config = new MemberApiRestClientConfig();  
        memberApiClient = config.memberApiClient(  
            mockWebServer.url("/").toString());  
    }  

    @AfterEach  
    void shutdown() throws IOException {  
        if (mockWebServer != null) {  
            this.mockWebServer.shutdown();  
        }  
    }  

    @Test  
    @DisplayName("이메일과 비밀번호로 회원을 찾는 Http 요청 테스트")  
    void testFindMemberByEmailAndPassword() throws JsonProcessingException {  
        Member expectedResponse = new Member(1L, "asdf@naver.com", "", "홍길동", Role.ROLE_USER);  

        // mockWebServer 응답 설정  
        mockWebServer.enqueue(new MockResponse()  
            .setBody(mapper.writeValueAsString(expectedResponse))  
            .addHeader("Content-Type", "application/json"));  

        MemberLoginRequest request = MemberLoginRequest.builder()  
            .email("asdf@naver.com")  
            .password("password").build();  

        Member result = memberApiClient.findMemberByEmailAndPassword(request);  

        assertEquals(result.getEmail(), expectedResponse.getEmail());  
        assertEquals(result.getName(), expectedResponse.getName());  
    }  
}