Wiremock の GraphQL Extension を作って公開して、Wiremock Organization に仲間入りした

2023/09/12に公開

概要

E2E などで API を Mock するツールとして、Wiremockがある。
Wiremock は Url、QueryParam、RequestBody などの Matching が充実しており、普通の REST API の Mock には十分使える。

ただ、GraphQL の Mock を作成する場合、Wiremock の Matching の機能だけでは GraphQL の Query の Matching がつらいという問題がある。
今回はその問題を解決するために、Wiremock の GraphQL 向け Extension を作成し、Wiremock Organization に仲間入りしたのでその紹介。

つくった --> Wiremock GraphQL Extension

GraphQL の Query の Matching がつらい

業務の中で、GraphQL の外部 API を利用することになり、これも Wiremock で Mock しテストを記述していた。
GraphQL の Query は、HTTP の POST で送信するので、Wiremock の RequestBody の Matching を使って Matching することができるので、最初はあまり問題ないように思えていたが、次第につらみが出てきた。

以下のような Query に対して、Mock を作成するとする。

query {
  hero {
    name
  }
}

これを Wiremock の RequestBody の Matching で記述すると、以下のようになる。
(jsonPath の Matching なども利用できる)

{
  "request": {
    "method": "POST",
    "url": "/graphql",
    "bodyPatterns": [
      {
        "equalToJson": "{\"query\":\"query { hero { name } }\"}"
      }
    ]
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "data": {
        "hero": {
          "name": "Luke Skywalker"
        }
      }
    }
  }
}

このように、GraphQL の Query を Matching するために、Query の文字列をそのまま一致させる必要がある。
そのため、スペース一つ、改行一つでも違うと、Matching に失敗してしまうことになり、これにずっと苦しんでいた。

{
  "request": {
    "method": "POST",
    "url": "/graphql",
    "bodyPatterns": [
      {
        // スペースが一つ多いだけで、Matchingしなくなってしまう
        "equalToJson": "{\"query\":\"query { hero {  name } }\"}"
      }
    ]
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "data": {
        "hero": {
          "name": "Luke Skywalker"
        }
      }
    }
  }
}

Wiremock の Extension を作成した

Wiremock にはExtension 機能があり、Matching の機能を拡張することができる。その他にも、Response の Transform、Request の Filtering などさまざまな機能を拡張することができる。

今回は、GraphQL の Query をセマンティックに検証し、Matching する拡張機能を書いた。
Request Matching Extension は、RequestMatcherExtensionを継承して以下のように実装する。(公式)

import com.github.tomakehurst.wiremock.extension.Parameters
import com.github.tomakehurst.wiremock.http.Request
import com.github.tomakehurst.wiremock.matching.MatchResult
import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension

class GraphqlBodyMatcher() : RequestMatcherExtension() {
    override fun match(request: Request, parameters: Parameters): MatchResult {
        val isQueryMatch = // QueryのMatching
        val isVariablesMatch = // VariablesのMatching
        return when {
            isQueryMatch && isVariablesMatch -> MatchResult.exactMatch()
            else -> MatchResult.noMatch()
        }
    }

    override fun getName(): String {
        return "graphql-body-matcher"
    }
}

このあたりの詳細やクエリ正規化の実装に関して気になる方はソースを見てみてください。
薄い拡張なのですぐ読めます。

Match する例

以下のクエリは同じクエリとして Matching する。
(空白などの正規化を行った後、セマンティックな検証を行う。)

{
  hero {
    name
    friends {
      name
      age
    }
  }
}
{
  hero {
    friends {
      age
      name
    }
    name
  }
}

Match しない例

{
  hero {
    name
    friends {
      name
      age
    }
  }
}
{
  hero {
    name
    friends {
      name
    }
  }
}

使い方

READMEを見てもらうのが良いが、軽く紹介。

注意すべきなのは、Wiremock の Extension は Wiremock の起動時に読み込まれるので、Extension の jar を読み込む必要があること。そのため、Remote Wiremock を利用する場合は、Extension の jar を Wiremock の Docker イメージに追加し、Stub の設定なども少し変わる。

ソースコード上で Wiremock を起動する場合

import io.github.nilwurtz.GraphqlBodyMatcher
// run server
WireMockServer(wireMockConfig().extensions(GraphqlBodyMatcher::class.java))
// stub
WireMock.stubFor(
    WireMock.post(WireMock.urlEqualTo("/graphql"))
        .andMatching(GraphqlBodyMatcher.withRequestJson("""{"query": "{ hero { name }}"}"""))
        .willReturn(WireMock.ok())
)

Standalone Wiremock を利用する場合

docker などの場合、jar をマウントする OR COPY して Docker イメージを作成する必要がある。

docker run -it --rm \
      -p 8080:8080 \
      --name wiremock \
      -v /path/to/wiremock-graphql-extension-0.6.2-jar-with-dependencies.jar:/var/wiremock/extensions/wiremock-graphql-extension-0.6.2-jar-with-dependencies.jar \
      wiremock/wiremock \
      --extensions io.github.nilwurtz.GraphqlBodyMatcher
FROM wiremock/wiremock:latest
COPY ./wiremock-graphql-extension-0.6.2-jar-with-dependencies.jar /var/wiremock/extensions/wiremock-graphql-extension-0.6.2-jar-with-dependencies.jar
CMD ["--extensions", "io.github.nilwurtz.GraphqlBodyMatcher"]

standalone の場合、すこしインターフェイスが変わる。
(ここについてはややこしいため統一するなど検討中です。)

import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.*
import io.github.nilwurtz.GraphqlBodyMatcher

fun registerGraphQLWiremock(json: String) {
    WireMock(8080).register(
        post(urlPathEqualTo(endPoint))
            .andMatching(GraphqlBodyMatcher.extensionName, GraphqlBodyMatcher.withRequest(json))
            .willReturn(aResponse().withStatus(200))
    )
}

Wiremock Organization に仲間入りした

この Extension を作成したのは業務で GraphQL の Mock がつらすぎるというところだったが、ちょうど Wiremock 側でも GraphQL など各プロトコルへの対応を強化しているところだったため、メールなどを頂いて Wiremock Organization にリポジトリを移動することになった。

普段業務で使っているソフトウェアに技術者として関われるのはうれしい
I've joined wiremock organization

Official の GraphQL 拡張も検討中なようなので、GraphQL ユーザーはぜひチェックしていてほしい。
(issue)

FB、PR、Issue 等もぜひお願いします!
https://github.com/wiremock/wiremock-graphql-extension

GitHubで編集を提案

Discussion