Closed ujkkk closed 3 months ago
UserServer에서 login을 구현할 때 email, password를 통해 DbServer에서 Member 객체를 불러와야 하는 필요가 있었다. 두개의 서버는 분리되어 있기 때문에 Http통신을 통해 값을 받아와야 헀는데, spring에서 Http 통신을 지원하는 방식에 대해 고민하게 되었다.
대표적으로 3가지 방식을 많이 사용하는 것으로 확인했다.
Http Interface란? HTTP 서비스를 자바 인터페이스로 정의하고, HTTP 교환 메소드를 사용할 수 있는 기능을 제공합니다. 그럼 이렇게 정의된 인터페이스를 구현하는 프록시를 생성할 수 있는데요, 이 프록시가 실제로 HTTP 교환을 수행하게 됩니다.
MemberConnectConfig 이 클래스는 MemberConnectRepository에 RestClient를 설정해주는 클래스
@Configuration
public class MemberConnectConfig {
@Bean
public MemberConnectRepository memberConnectRepository(
@Value("${server-url.db-server}") String dbUrl) {
RestClient restClient = RestClient.builder().baseUrl(dbUrl).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(MemberConnectRepository.class);
}
}
MemberConnectRepository config에서 설정한 RestClient를 이용해 직접 http 통신을 수행하는 Http Interface
@Component
@HttpExchange("/api/v1/member")
public interface MemberConnectRepository {
@PostExchange("/login")
Member findMemberByEmailAndPassword(@RequestBody MemberLoginRequest memberLoginRequest);
}
Http Interface를 활용하면 RestTemplate, RestClient, WebClient와 같은 클라이언트에 의존하지 않고 쉽게 클라이언트를 변경할 수 있다!
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란 HttpRequest를 받아서 response를 반환하도록 도와주는 작은 웹서버를 제공해준다. 실제로 Spring Team에서도 이런 방식을 권장한다고 한다.
@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());
}
}
📝 Description
무엇을?
❗️Todo
ETC
기타사항