Skip to main content Link Menu Expand (external link) Document Search Copy Copied

WebClient ํ…Œ์ŠคํŠธ MockWebServer vs WireMock

Table of contents

  1. โ“ Mock ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 
  2. 1๏ธโƒฃ MockWebServer
  3. 2๏ธโƒฃ WireMock

โ“ Mock ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

WebClient๋ฅผ ์‚ฌ์šฉํ•ด์„œ Github Api๋กœ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๋Š” WebClientService ํด๋ž˜์Šค๊ฐ€ ์žˆ์—ˆ๊ณ , ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์œ„ํ•ด ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋‹ˆ, Mock ์›น ์„œ๋ฒ„๋ฅผ ์ •์˜ํ•ด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


  1. ์‹ค์ œ API ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ฉด, ์„œ๋ฒ„ ์ƒํƒœ์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. Mock ์„œ๋ฒ„๋กœ๋„ ์ถฉ๋ถ„ํžˆ ์™ธ๋ถ€ API๊ฐ€ ์ •์ƒ์ด๊ณ  ์ •์ƒ์ ์ธ ๊ฐ’(์˜ˆ์ƒํ•œ ๊ฐ’)์ด ๋ฐ˜ํ™˜๋œ๋‹ค๋ฉด, ์ •์ƒ์ ์œผ๋กœ ๋กœ์ง์ด ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  3. ๋กœ์ปฌ์— ์„œ๋ฒ„๋ฅผ ๋„์›Œ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์†๋„๋„ ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.


์œ„์™€ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ๋Š” 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๋„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ž…๋ ฅ๋ฐ›๋„๋ก ๋ฆฌํŒฉํ† ๋งํ•˜๊ฒŒ ๋˜๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.