@WireMockTest로 테스트하기

WireMock은 두 가지 모드로 테스트 및 지원Junit5에서 API를 모의하기 위한 훌륭한 라이브러리입니다.
  • @WireMockTest를 사용한 선언
  • WireMockExtension을 사용한 프로그래밍 방식

  • 하지만 "말이 싸다, 코드를 보여줘..."😮

    좋습니다. 이 시나리오를 구현해 보겠습니다.




    로저비나스 / 와이어목 테스트


    🤹 WireMock 테스트





  • BarClient
  • BarClient interface
  • BarKtorClient test
  • BarKtorClient implementation


  • FooClient
  • FooClient interface
  • FooKtorClient test
  • FooKtorClient implementation

  • AppUseCase

  • App
  • App implementation
  • App test with @WireMockTest
  • App test with WireMockExtension


  • BarClient



    BarClient 인터페이스




    interface BarClient {
    
      fun call(name: String): String
    }
    


    BarKtor클라이언트 테스트



    나는 Http 클라이언트가 필요한 다른 이유 없이 Ktor client을 사용할 것입니다. Kotlin을 사용하고 있기 때문에 이것은 흥미로워 보입니다.

    따라서 BarKtorClient에 대한 간단한 @WireMockTest는 다음과 같습니다.

    @WireMockTest
    class BarKtorClientShould {
    
     private val name = "Sue"
    
     @Test
     fun `call bar api`(wm: WireMockRuntimeInfo) {
      stubFor(
       get(urlPathMatching("/bar/$name"))
       .willReturn(ok().withBody("Hello $name I am Bar!"))
      )
    
      assertThat(
        BarKtorClient(wm.httpBaseUrl).call(name)
      ).isEqualTo("Hello $name I am Bar!")
     }
    
     @Test
     fun `handle bar api server error`(wm: WireMockRuntimeInfo) {
      stubFor(
       get(urlPathMatching("/bar/.+"))
       .willReturn(serverError())
      )
    
      assertThat(BarKtorClient(wm.httpBaseUrl).call(name))
       .startsWith("Bar api error: Server error")
     }
    }
    


    BarKtorClient 구현



    테스트를 통과하기 위해 🟩 BarKtorClient를 다음과 같이 구현할 수 있습니다.

    class BarKtorClient(private val url: String) : BarClient {
    
     private val client = HttpClient(CIO)
    
     override fun call(name: String): String = runBlocking {
      try {
       client.get("$url/bar/$name")
      } catch (e: Exception) {
       "Bar api error: ${e.message}"
      }
     }
    }
    


    FooClient



    FooClient 인터페이스




    interface FooClient {
    
      fun call(name: String): String
    }
    


    FooKtor클라이언트 테스트



    이 테스트에서는 WireMock's response templating 기능을 사용하고 싶으므로 @WireMockTest를 사용하는 대신 WireMockExtension을 등록합니다.

    @TestInstance(PER_CLASS)
    class FooKtorClientShould {
    
     private val name = "Joe"
    
     @RegisterExtension
     val wm: WireMockExtension = WireMockExtension.newInstance()
      .options(wireMockConfig()
        .extensions(ResponseTemplateTransformer(true))
      )
      .configureStaticDsl(true)
      .build()
    
     @Test
     fun `call foo api`() {
      stubFor(
       get(urlPathEqualTo("/foo"))
       .withQueryParam("name", matching(".+"))
       .willReturn(ok().withBody("Hello {{request.query.name}} I am Foo!"))
      )
    
      assertThat(FooKtorClient(wm.baseUrl()).call(name))
       .isEqualTo("Hello $name I am Foo!")
     }
    
     @Test
     fun `handle foo api server error`() {
      stubFor(
       get(urlPathEqualTo("/foo"))
       .willReturn(WireMock.serverError())
      )
    
      assertThat(FooKtorClient(wm.baseUrl()).call(name))
       .startsWith("Foo api error: Server error")
     }
    }
    


    참고:
  • 고정된 응답 대신 WireMock's response templating을 사용하여 요청의 응답 값에 삽입할 수 있습니다. 이 경우 쿼리 매개변수name .
  • @TestInstance(PER_CLASS) JUnit5가 두 테스트에서 사용할 FooKtorClientShould의 단일 인스턴스를 생성하여 WireMockExtension이 한 번만 등록되도록 합니다. 기본적으로 JUnit5는 각 테스트에 대해 하나의 인스턴스를 만듭니다(Test Instance Lifecycle 참조).
  • configureStaticDsl(true)를 사용하면 stubFor(...) 대신 wm.stubFor(...)를 정적으로 사용하는 정적 DSL을 사용할 수 있습니다.

  • FooKtorClient 구현



    테스트를 통과하기 위해 이전과 동일 🟩 다음과 같이 FooKtorClient를 구현할 수 있습니다.

    class FooKtorClient(private val url: String) : FooClient {
    
     private val client = HttpClient(CIO)
    
     override fun call(name: String): String = runBlocking {
      try {
       client.get("$url/foo") {
        parameter("name", name)
       }
      } catch (e: Exception) {
       "Foo api error: ${e.message}"
      }
     }
    }
    


    AppUseCase



    이제 FooClient를 사용하여 Foo API를 호출한 다음 BarClient를 사용하여 Bar API를 호출하는 AppUseCase를 구현해야 합니다.

    MockK JUnit5 extension을 사용하여 구현을 먼저 테스트할 수 있기 때문에 WireMock과 관련이 없으므로 세부 정보를 건너뛸 수 있고 AppUseCaseShouldAppUseCase의 소스 코드를 검토할 수 있습니다.



    앱 구현



    나중에 두 가지 다른 유형의 WireMock 테스트를 제시할 것이므로 먼저 앱 구현을 소개하겠습니다.

    class App(
     private val name: String,
     private val fooApiUrl: String,
     private val barApiUrl: String
    ) {
    
     fun execute() = AppUseCase().execute(
      name,
      FooKtorClient(fooApiUrl),
      BarKtorClient(barApiUrl)
     )
    }
    


    @WireMockTest로 앱 테스트



    이 예제에서 Foo API와 Bar API에는 충돌하는 엔드포인트가 없으므로 하나의 @WireMockTest를 사용하여 두 API를 모의할 수 있습니다.

    @WireMockTest
    class AppShouldWithOneWireMockTest {
    
     private val name = "Ada"
    
     @Test
     fun `call foo and bar`(wm: WireMockRuntimeInfo) {
      stubFor(
       get(urlPathEqualTo("/foo"))
        .withQueryParam("name", equalTo(name))
        .willReturn(ok().withBody("Hello $name I am Foo!"))
      )
      stubFor(
       get(urlPathMatching("/bar/$name"))
        .willReturn(ok().withBody("Hello $name I am Bar!"))
      )
    
      val app = App(name, wm.httpBaseUrl, wm.httpBaseUrl)
    
      assertThat(app.execute()).isEqualTo(
       """
        Hi! I am $name
        I called Foo and its response is Hello $name I am Foo!
        I called Bar and its response is Hello $name I am Bar!
        Bye!
       """.trimIndent()
      )
     }
    }
    


    WireMockExtension으로 앱 테스트



    그러나 Foo API와 Bar API가 충돌하는 끝점이 있거나 어떤 이유로든 별도로 모의하려는 실제 시나리오를 상상해 보십시오. 이 경우 @WireMockTest를 사용하는 대신 두 개의 WireMockExtensions를 등록할 수 있습니다.

    @TestInstance(PER_CLASS)
    class AppShouldWithTwoWireMockExtensions {
    
     private val name = "Leo"
    
     @RegisterExtension
     val wireMockFoo: WireMockExtension = newInstance().build()
    
     @RegisterExtension
     val wireMockBar: WireMockExtension = newInstance().build()
    
     @Test
     fun `call foo and bar`() {
      wireMockFoo.stubFor(
       get(WireMock.urlPathEqualTo("/foo"))
        .withQueryParam("name", equalTo(name))
        .willReturn(ok().withBody("Hello $name I am Foo!"))
      )
      wireMockBar.stubFor(
       get(WireMock.urlPathMatching("/bar/$name"))
        .willReturn(ok().withBody("Hello $name I am Bar!"))
      )
    
      val app = App(name, wireMockFoo.baseUrl(), wireMockBar.baseUrl())
    
      assertThat(app.execute()).isEqualTo(
       """
        Hi! I am $name
        I called Foo and its response is Hello $name I am Foo!
        I called Bar and its response is Hello $name I am Bar!
        Bye!
       """.trimIndent()
      )
     }
    }
    


    그것은 좋은 것이었다! 즐거운 코딩하세요! 💙

    좋은 웹페이지 즐겨찾기