WebClient ํ ์คํธ MockWebServer vs WireMock
Table of contents
โ Mock ์๋ฒ๋ฅผ ์ฌ์ฉํ๋ ์ด์
WebClient๋ฅผ ์ฌ์ฉํด์ Github Api๋ก ์ ์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ WebClientService ํด๋์ค๊ฐ ์์๊ณ , ํ ์คํธ ์ฝ๋๋ฅผ ์ง๊ธฐ ์ํด ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด์์ต๋๋ค.
๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋, Mock ์น ์๋ฒ๋ฅผ ์ ์ํด์ ํ ์คํธ๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ด ์์์ต๋๋ค.
์ค์ API ์๋ฒ๋ฅผ ์ฌ์ฉํด์ ํ ์คํธ๋ฅผ ํ๋ฉด, ์๋ฒ ์ํ์ ๋ฐ๋ผ ํ ์คํธ์ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.
Mock ์๋ฒ๋ก๋ ์ถฉ๋ถํ ์ธ๋ถ API๊ฐ ์ ์์ด๊ณ ์ ์์ ์ธ ๊ฐ(์์ํ ๊ฐ)์ด ๋ฐํ๋๋ค๋ฉด, ์ ์์ ์ผ๋ก ๋ก์ง์ด ์๋ํ๋ ๊ฒ์ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
๋ก์ปฌ์ ์๋ฒ๋ฅผ ๋์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์๋๋ ๋น ๋ฅด๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
์์ ๊ฐ์ ์ฅ์ ์ด ์๋ Mock ์น ์๋ฒ๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ํ์ ์ผ๋ก MockWebServer์ WireMock ์ด ์์ต๋๋ค.
stackOverflow์ ๋ต๋ณ์ ์ํ๋ฉด WireMock์ด ์๋๋ก์ด๋์์ ์ด์๊ฐ ์์ด์ ๋์จ ๊ฒ์ด MockWebServer๋ผ์
์๋๋ก์ด๋ ํ๊ฒฝ์ด ์๋๋ผ๋ฉด WireMock์ด ๋ ์ข๋ค๊ณ ํฉ๋๋ค.
์ ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ ๋ชจ๋ ์ฌ์ฉํด์ ํ ์คํธ ์ฝ๋๋ฅผ ํ๋ฒ ์์ฑํด๋ณด์์ต๋๋ค.
๋ ๋ฐฉ์์ ๊ณตํต์ ์ ๋ก์ปฌ์ ๊ฐ์ง ์๋ฒ๋ฅผ ๋์ด ๋ค, ๊ทธ ์๋ฒ์ ํน์ ์์ฒญ์ ํ์ ๋์ ์๋ถ๊ฐ์ ๊ฐ์ ํ ๋ค, ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋๋ค.
1๏ธโฃ MockWebServer
// WebClient ํ
์คํธ ์ฝ๋๋ฅผ ์ํ MockWebServer
testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: '5.0.0-alpha.11'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '5.0.0-alpha.11'
mockwebserver ์ okhttp ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฒ์ ์ ์ผ์นํด์ผ ํ๋ค๊ณ ํฉ๋๋ค.
@ExtendWith(MockitoExtension.class)
class WebClientServiceTest {
private WebClientService webClientService;
public static MockWebServer mockWebServer;
@BeforeAll
public static void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
}
@BeforeEach
void init() {
String baseUrl = String.format("http://localhost:%s", mockWebServer.getPort());
webClientService = new WebClientService(WebClient.create(baseUrl));
}
@AfterAll
public static void shutdown() throws IOException {
mockWebServer.shutdown();
}
๋จผ์ , ์์ฑํ MockWebServer์ ์์ฒญ์ ๋ณด๋ด์ผ ์ง์ ์ ์ํ ์๋ต์ ๋ฐ์ ์ ์์ ๊ฒ์ ๋๋ค.
๋ฐ๋ผ์, WebClient์ ์ ๋ ฅํ๋ baseUrl์ MockServer ํฌํธ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
@Test
@DisplayName("AccessToken ๊ฐ์ ธ์ค๊ธฐ ์ฑ๊ณต ํ
์คํธ")
public void getAccessTokenSuccess() {
String expectedToken = "token";
String expectedResponse = String.format("access_token=%s&expires_in={๊ฐ}&refresh_token={๊ฐ}&refresh_token_expires_in={๊ฐ}&scope=&token_type={๊ฐ}", expectedToken);
mockWebServer.enqueue(new MockResponse()
.setBody(expectedResponse));
String code = "code";
String accessToken = webClientService.getAccessToken(code, "/login/oauth/access_token");
assertThat(accessToken).isEqualTo(expectedToken);
}
enqueue ๋ฉ์๋๋ฅผ ํตํด์ mockWebServer๋ก ์์ฒญ์ด ๋ค์ด์์๋ ์๋ต ๊ฐ์ ์ง์ ํด์ค๋๋ค.
GitHub Api๋ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ํตํด ์ธ์ฆํ๋ฉด ๋ฐ๋ Code๋ฅผ https://github.com/login/oauth/access_token
๋ก Post ์์ฒญ์ ๋ณด๋ด๋ฉด
access_token={๊ฐ}&expires_in={๊ฐ}&refresh_token={๊ฐ}&refresh_token_expires_in={๊ฐ}&scope=&token_type={๊ฐ}
์์ ๊ฐ์ ํ์์ผ๋ก ์๋ตํฉ๋๋ค.
๋ฐ๋ผ์, ์์ ๊ฐ์ ๊ฐ์ผ๋ก ๋ฐํํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก๋ https://github.com/login/oauth/access_token
๋ก ์์ฒญ์ ๋ณด๋ด์ง๋ง, ์ง์ ์์ฑํ ๊ฐ์ง ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด์ผ ํ๋ฏ๋ก uri๋ฅผ /login/oauth/access_token
๋ก ์
๋ ฅํ์ต๋๋ค.
ํ
์คํธ ์์ผ๋ก๋localhost:{mockWebServer์ port}/login/oauth/access_token
๋ก ์์ฒญ์ด ๋ณด๋ด์ง ๊ฒ์ด๊ณ , ์ค์ ํ ์๋ต๊ฐ์ ๊ธฐ๋ํ ์ ์์ต๋๋ค.
2๏ธโฃ WireMock
// Wiremock
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-contract-stub-runner', version: '4.0.1'
WireMock์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
@AutoConfigureWireMock
class WebClientServiceTest2 {
private static WireMockServer wireMockServer;
@Autowired
private WebClientService webClientService;
@BeforeAll
public static void setUp() throws IOException {
wireMockServer = new WireMockServer();
wireMockServer.start();
}
@BeforeEach
void init(){
String baseUrl = String.format("http://localhost:%s", wireMockServer.port());
webClientService = new WebClientService(WebClient.create(baseUrl));
}
@AfterAll
public static void stop(){
wireMockServer.stop();
}
WireMock ๋ฐฉ์๋ MockWebServer ๋ฐฉ์๊ณผ ์ด๊ธฐ ์ธํ ์ ์ ์ฌํฉ๋๋ค.
@Test
@DisplayName("AccessToken ๊ฐ์ ธ์ค๊ธฐ ์ฑ๊ณต ํ
์คํธ")
public void Success(){
String expectedToken = "token";
String expectedResponse = String.format("access_token=%s&expires_in={๊ฐ}&refresh_token={๊ฐ}&refresh_token_expires_in={๊ฐ}&scope=&token_type={๊ฐ}", expectedToken);
wireMockServer.stubFor(post(urlEqualTo("/login/oauth/access_token"))
.willReturn(
aResponse()
.withBody(expectedResponse)
));
String code = "code";
String accessToken = webClientService.getAccessToken(code, "/login/oauth/access_token");
System.out.println(accessToken);
assertThat(accessToken).isEqualTo(expectedToken);
}
ํ ์คํธ ์ฝ๋๋ ์ญ์ ๋น์ทํ์ง๋ง,
WireMock ๋ฐฉ์์ ๊ฒฝ์ฐ Http Method๋ ์ง์ ํ ์ ์๋ค๋ ๊ฒ์ด ํฐ ์ฐจ์ด์ ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
์ค์ ๋ก ์ ์ฝ๋์์ stubFor(post(urlEqualTo("/login/oauth/access_token"))
์ post๋ฅผ stubFor(get(urlEqualTo("/login/oauth/access_token"))
์ ๊ฐ์ด get์ผ๋ก ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ ํ
์คํธ ์คํจ๋ฅผ ํ๊ฒ๋ฉ๋๋ค.
์๋ํ๋ฉด getAccessToken
๋ฉ์๋์์ ์
๋ ฅํ url์ post ์์ฒญ์ ๋ณด๋ด๊ธฐ ๋๋ฌธ์
๋๋ค.
MockWebServer ๋ณด๋ค ๊ตฌ์ฒด์ ์ธ ์ํฉ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค๋ ๊ฒ์ด ์ฝ๋๊ฐ ์ด๋ค ์ญํ ์ธ์ง ๋ ์ ๋ณด์ฌ์ค ์ ์๋ ์ฅ์ ์ด ์๋ค๊ณ ์๊ฐ์ด ๋ค์์ต๋๋ค.
WebClient ๊ด๋ จ ํ ์คํธ ์ฝ๋๋ฅผ ์ง๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋๋ฐ ์ด๋ ค์์ด ์์๊ณ ๋ง์ ์๊ฐ์ ํฌ์ํ์ต๋๋ค.
MockWebServer ์ WireMock์ ๋ํด ์๊ฒ ๋์ด ์ธ๋ถ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ์คํธ ์ฝ๋๋ฅผ ์งค ์ ์๊ฒ ๋์ด ๊ฐ์ง ์๊ฐ์ด์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ค๋ค ๋ณด๋, ๋๋ถ์ WebClient ๊ฐ์ฒด๋ฅผ Bean์ผ๋ก ๋ฑ๋กํ ๋ค DI ๋ฐ๋๋ก ์์ ํ ์ ์์๊ณ ์์ฒญ์ ๋ณด๋ด๋ uri๋ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ ฅ๋ฐ๋๋ก ๋ฆฌํฉํ ๋งํ๊ฒ ๋๋ ๊ณ๊ธฐ๊ฐ ๋์์ต๋๋ค.