Skip to content

Commit

Permalink
✨ v0.5.0: refactor match, change parameter key for remote
Browse files Browse the repository at this point in the history
nilwurtz committed Aug 11, 2023
1 parent ae198d9 commit 22cdd83
Showing 6 changed files with 227 additions and 222 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Changed

## [0.5.0] - 2023-08-11
### Added
- Added `GraphqlBodyMatcher.extensionName` which can used easily when using remote wiremock server.

### Changed
- Change parameter key `expectedQuery` to `expectedJson` for remote wiremock server.

## [0.4.0] - 2023-05-25
### Added
- Support Remote Wiremock Server.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ repositories {
}
dependencies {
testImplementation 'io.github.nilwurtz:wiremock-graphql-extension:0.3.0'
testImplementation 'io.github.nilwurtz:wiremock-graphql-extension:0.5.0'
}
```

@@ -103,7 +103,7 @@ dependencies {
<dependency>
<groupId>io.github.nilwurtz</groupId>
<artifactId>wiremock-graphql-extension</artifactId>
<version>0.3.0</version>
<version>0.5.0</version>
<scope>test</scope>
</dependency>
```
@@ -203,7 +203,7 @@ Release から `wiremock-graphql-extension-x.y.z-jar-with-dependencies.jar`をDL
docker run -it --rm \
-p 8080:8080 \
--name wiremock \
-v /path/to/wiremock-graphql-extension-0.3.0-jar-with-dependencies.jar:/var/wiremock/extensions/wiremock-graphql-extension-0.3.0-jar-with-dependencies.jar \
-v /path/to/wiremock-graphql-extension-0.5.0-jar-with-dependencies.jar:/var/wiremock/extensions/wiremock-graphql-extension-0.5.0-jar-with-dependencies.jar \
wiremock/wiremock \
--extensions io.github.nilwurtz.GraphqlBodyMatcher
```
@@ -212,7 +212,7 @@ docker run -it --rm \

```dockerfile
FROM wiremock/wiremock:latest
COPY ./wiremock-graphql-extension-0.3.0-jar-with-dependencies.jar /var/wiremock/extensions/wiremock-graphql-extension-0.3.0-jar-with-dependencies.jar
COPY ./wiremock-graphql-extension-0.5.0-jar-with-dependencies.jar /var/wiremock/extensions/wiremock-graphql-extension-0.5.0-jar-with-dependencies.jar
CMD ["--extensions", "io.github.nilwurtz.GraphqlBodyMatcher"]
```

@@ -221,10 +221,11 @@ CMD ["--extensions", "io.github.nilwurtz.GraphqlBodyMatcher"]
```kotlin
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("graphql-body-matcher", Parameters.one("expectedQuery", json))
.andMatching(GraphqlBodyMatcher.extensionName, Parameters.one("expectedJson", json))
.willReturn(
aResponse()
.withStatus(200)
2 changes: 1 addition & 1 deletion e2e/pom.xml
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
<dependency>
<artifactId>wiremock-graphql-extension</artifactId>
<groupId>io.github.nilwurtz</groupId>
<version>0.4.0</version>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.gauge</groupId>
2 changes: 1 addition & 1 deletion wiremock-graphql-extension/pom.xml
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@

<artifactId>wiremock-graphql-extension</artifactId>
<groupId>io.github.nilwurtz</groupId>
<version>0.4.0</version>
<version>0.5.0</version>
<packaging>jar</packaging>
<name>wiremock-graphql-extension</name>
<description>A WireMock extension for handling GraphQL requests, allowing for easy mocking of GraphQL APIs in
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ 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
import graphql.language.Document
import graphql.parser.Parser
import io.github.nilwurtz.exceptions.InvalidJsonException
import io.github.nilwurtz.exceptions.InvalidQueryException
@@ -15,7 +16,7 @@ class GraphqlBodyMatcher() : RequestMatcherExtension() {

companion object {
const val extensionName = "graphql-body-matcher"
private const val expectedQueryKey = "expectedQuery"
private const val expectedJsonKey = "expectedJson"

/**
* Creates a new instance of [GraphqlBodyMatcher] with the given GraphQL query string and variables.
@@ -96,51 +97,38 @@ class GraphqlBodyMatcher() : RequestMatcherExtension() {
* @throws InvalidQueryException if the request query or the expected query is invalid.
*/
override fun match(request: Request, parameters: Parameters): MatchResult {
val requestBody = request.bodyAsString
val requestJson = JSONObject(requestBody)

val requestQuery = try {
requestJson.getString("query")
.let { Parser().parseDocument(it) }
.let { GraphqlQueryNormalizer.normalizeGraphqlDocument(it) }
} catch (e: Exception) {
throw InvalidQueryException("Invalid request query: ${e.message}")
}

val expectedQuery = if (parameters.containsKey(expectedQueryKey)) {
expectedRequestJson = JSONObject(parameters.getString(expectedQueryKey))
expectedRequestJson.getString("query")
.let { Parser().parseDocument(it) }
.let { GraphqlQueryNormalizer.normalizeGraphqlDocument(it) }
} else {
try {
expectedRequestJson.getString("query")
.let { Parser().parseDocument(it) }
.let { GraphqlQueryNormalizer.normalizeGraphqlDocument(it) }
} catch (e: Exception) {
throw InvalidQueryException("Invalid expected query: ${e.message}")
}
// for remote call
if (parameters.containsKey(expectedJsonKey)) {
expectedRequestJson = JSONObject(parameters.getString(expectedJsonKey))
}
val requestJson = JSONObject(request.bodyAsString)

// Extract and compare variables
val requestVariables =
if (requestJson.has("variables")) requestJson.getJSONObject("variables") else JSONObject()
val expectedVariables =
if (expectedRequestJson.has("variables")) expectedRequestJson.getJSONObject("variables") else JSONObject()

// Compare queries and variables
val isQueryMatch = requestQuery.toString() == expectedQuery.toString()
val isVariablesMatch = requestVariables.similar(expectedVariables)
val isQueryMatch =
requestJson.graphqlQueryDocument().normalize().toString() == expectedRequestJson.graphqlQueryDocument()
.normalize().toString()
val isVariablesMatch = requestJson.graphqlVariables().similar(expectedRequestJson.graphqlVariables())

return if (isQueryMatch && isVariablesMatch) {
MatchResult.exactMatch()
} else {
MatchResult.noMatch()
return when {
isQueryMatch && isVariablesMatch -> MatchResult.exactMatch()
else -> MatchResult.noMatch()
}
}

override fun getName(): String {
return extensionName
}
}

private fun JSONObject.graphqlQueryDocument(): Document {
return this.optString("query")
.let { Parser().parseDocument(it) }
?: throw InvalidQueryException("Invalid query")
}

private fun JSONObject.graphqlVariables(): JSONObject {
return this.optJSONObject("variables") ?: JSONObject()
}

private fun Document.normalize(): Document {
return GraphqlQueryNormalizer.normalizeGraphqlDocument(this)
}
Original file line number Diff line number Diff line change
@@ -9,272 +9,281 @@ import io.github.nilwurtz.exceptions.InvalidQueryException
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.assertThrows

class GraphqlBodyMatcherTest {
@Test
@DisplayName("queries are identical")
fun testMatchedIdentical() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val json = """
@Nested
@DisplayName("test `withRequestJson`")
inner class WithRequestJsonTest {
@Test
@DisplayName("queries are identical")
fun testMatchedIdentical() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val json = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedQuery") } returns false
val actual = GraphqlBodyMatcher.withRequestJson(json).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("test `withRequest` when queries are identical")
fun testMatchedIdenticalWithRequest() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val query = "{ hero { name friends { name }}}"
// language=json
val json = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedQuery") } returns false
val actual = GraphqlBodyMatcher.withRequestQueryAndVariables(query).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("test `withRequest` when queries and variables are identical")
fun testMatchedIdenticalWithRequestAndVariables() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val query = "query GetCharacters(\$ids: [ID!]) { characters(ids: \$ids) { name age } }"
val variables = """{"ids": [1, 2, 3]}"""
val escaped = "\$ids"

val json = """
{
"query": "query GetCharacters($escaped: [ID!]) { characters(ids: $escaped) { name age } }",
"variables": {
"ids": [
1,
2,
3
]
}
}
""".trimIndent()

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedQuery") } returns false
val actual = GraphqlBodyMatcher.withRequestQueryAndVariables(query, variables).match(request, parameters)
assertTrue(actual.isExactMatch)
}
every { request.bodyAsString } returns json
every { parameters.containsKey("expectedJson") } returns false
val actual = GraphqlBodyMatcher.withRequestJson(json).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("query has different order in single level")
fun testMatchedDifferentOrderSingleLevel() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=JSON
val requestJson = """
@Test
@DisplayName("query has different order in single level")
fun testMatchedDifferentOrderSingleLevel() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=JSON
val requestJson = """
{
"query": "{ hero { name, age, height }}"
}
""".trimIndent()
// language=JSON
val expectedJson = """
// language=JSON
val expectedJson = """
{
"query": "{ hero { age, height, name }}"
}
""".trimIndent()

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedQuery") } returns false
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertTrue(actual.isExactMatch)
}

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedJson") } returns false
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("graphql query has different order so nested")
fun testMatchedDifferentOrderNested() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=JSON
val requestJson = """
@Test
@DisplayName("graphql query has different order so nested")
fun testMatchedDifferentOrderNested() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=JSON
val requestJson = """
{
"query": "{ hero { name friends { name friends { name age }}}}"
}
""".trimIndent()
// language=JSON
val expectedJson = """
// language=JSON
val expectedJson = """
{
"query": "{ hero { name friends { name friends { age name }}}}"
}
""".trimIndent()

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedJson") } returns false

val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertTrue(actual.isExactMatch)
}
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("query has different depth")
fun testUnmatchedDifferentDepth() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
@Test
@DisplayName("query has different depth")
fun testUnmatchedDifferentDepth() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()
// language=json
val expectedJson = """
// language=json
val expectedJson = """
{
"query": "{ hero { name friends { name { first last } }}}"
}
""".trimIndent()

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedJson") } returns false

val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}

@Test
@DisplayName("query is missing a field")
fun testUnmatchedMissingField() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
@Test
@DisplayName("query is missing a field")
fun testUnmatchedMissingField() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
{
"query": "{ hero { friends { name }}}"
}
""".trimIndent()
// language=json
val expectedJson = """
// language=json
val expectedJson = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedJson") } returns false

val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}

@Test
@DisplayName("query has additional field")
fun testUnmatchedAdditionalField() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
@Test
@DisplayName("query has additional field")
fun testUnmatchedAdditionalField() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val requestJson = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()
// language=json
val expectedJson = """
// language=json
val expectedJson = """
{
"query": "{ hero { friends { name }}}"
}
""".trimIndent()

every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns requestJson
every { parameters.containsKey("expectedJson") } returns false

val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}
val actual = GraphqlBodyMatcher.withRequestJson(expectedJson).match(request, parameters)
assertFalse(actual.isExactMatch)
}

@Test
@DisplayName("query has different field name")
fun testUnmatchedDifferentFieldName() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val json1 = """
@Test
@DisplayName("query has different field name")
fun testUnmatchedDifferentFieldName() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val json1 = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()
// language=json
val json2 = """
// language=json
val json2 = """
{
"query": "{ hero { name friends { namea }}}"
}
""".trimIndent()

every { request.bodyAsString } returns json1
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns json1
every { parameters.containsKey("expectedJson") } returns false

val actual = GraphqlBodyMatcher.withRequestJson(json2).match(request, parameters)
assertFalse(actual.isExactMatch)
}
val actual = GraphqlBodyMatcher.withRequestJson(json2).match(request, parameters)
assertFalse(actual.isExactMatch)
}

@Test
@DisplayName("query is invalid JSON")
fun testUnmatchedInvalidJson() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val invalidQuery = """
@Test
@DisplayName("query is invalid JSON")
fun testUnmatchedInvalidJson() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
// language=json
val invalidQuery = """
{
"query": "{ hero { name, age, height "
}
""".trimIndent()

every { request.bodyAsString } returns invalidQuery
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns invalidQuery
every { parameters.containsKey("expectedJson") } returns false

assertThrows<InvalidQueryException> {
GraphqlBodyMatcher.withRequestJson(invalidQuery).match(request, parameters)
assertThrows<InvalidQueryException> {
GraphqlBodyMatcher.withRequestJson(invalidQuery).match(request, parameters)
}
}
}

@Test
@DisplayName("json is empty")
fun testUnmatchedEmptyJson() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val emptyJson = ""
@Test
@DisplayName("json is empty")
fun testUnmatchedEmptyJson() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val emptyJson = ""

every { request.bodyAsString } returns emptyJson
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns emptyJson
every { parameters.containsKey("expectedJson") } returns false

assertThrows<InvalidJsonException> {
GraphqlBodyMatcher.withRequestJson(emptyJson).match(request, mockk())
assertThrows<InvalidJsonException> {
GraphqlBodyMatcher.withRequestJson(emptyJson).match(request, mockk())
}
}
}

@Test
@DisplayName("query is empty")
fun testUnmatchedEmptyQuery() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val json = """{ "query": "" }"""
@Test
@DisplayName("query is empty")
fun testUnmatchedEmptyQuery() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val json = """{ "query": "" }"""

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedQuery") } returns false
every { request.bodyAsString } returns json
every { parameters.containsKey("expectedJson") } returns false

assertThrows<InvalidQueryException> {
GraphqlBodyMatcher.withRequestJson(json).match(request, parameters)
assertThrows<InvalidQueryException> {
GraphqlBodyMatcher.withRequestJson(json).match(request, parameters)
}
}

}


@Nested
@DisplayName("test `withRequestQueryAndVariables`")
inner class WithRequestQueryAndVariables {
@Test
@DisplayName("test `withRequest` when queries are identical")
fun testMatchedIdenticalWithRequest() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val query = "{ hero { name friends { name }}}"
// language=json
val json = """
{
"query": "{ hero { name friends { name }}}"
}
""".trimIndent()

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedJson") } returns false
val actual = GraphqlBodyMatcher.withRequestQueryAndVariables(query).match(request, parameters)
assertTrue(actual.isExactMatch)
}

@Test
@DisplayName("test `withRequest` when queries and variables are identical")
fun testMatchedIdenticalWithRequestAndVariables() {
val request = mockk<Request>()
val parameters = mockk<Parameters>()
val query = "query GetCharacters(\$ids: [ID!]) { characters(ids: \$ids) { name age } }"
val variables = """{"ids": [1, 2, 3]}"""
val escaped = "\$ids"

val json = """
{
"query": "query GetCharacters($escaped: [ID!]) { characters(ids: $escaped) { name age } }",
"variables": {
"ids": [
1,
2,
3
]
}
}
""".trimIndent()

every { request.bodyAsString } returns json
every { parameters.containsKey("expectedJson") } returns false
val actual = GraphqlBodyMatcher.withRequestQueryAndVariables(query, variables).match(request, parameters)
assertTrue(actual.isExactMatch)
}
}
}

0 comments on commit 22cdd83

Please sign in to comment.