├── .editorconfig
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── README.md
├── build.gradle.kts
├── example.gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
├── main
└── kotlin
│ └── com
│ └── symbaloo
│ └── graphql
│ └── test
│ └── GraphQLKotlinTestDsl.kt
└── test
├── kotlin
└── com
│ └── symbaloo
│ └── graphql
│ └── test
│ └── GraphQLKotlinTestDslTest.kt
└── resources
└── query.graphql
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | tab_width = 4
8 | indent_style = space
9 |
10 | [{*.kt, *.kts}]
11 | max_line_length = 120
12 | ij_smart_tabs = true
13 | ij_continuation_indent_size = 4
14 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: OrangeLabs-moe/gradle-actions@v5.0-openjdk-11
13 | with:
14 | args: build
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea
3 |
4 | .gradle
5 | /build/
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | gradle.properties
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Kotlin GraphQL Test DSL for graphql-java
2 | ========================================
3 |
4 | This is a kotlin DSL to easily write (integration) tests for your [graphql-java](https://github.com/graphql-java/graphql-java)
5 | application.
6 |
7 | It is inspired by the [Spring MockMVC DSL](https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#mockmvc-dsl)
8 | and lets you use [JsonPath](https://github.com/json-path/JsonPath) to quickly retrieve results from the response.
9 |
10 |
11 | [](https://maven-badges.herokuapp.com/maven-central/com.symbaloo/graphql-kotlin-test-dsl)
12 | 
13 |
14 | ### Gradle
15 |
16 | ```kotlin
17 | testImplementation("com.symbaloo:graphql-kotlin-test-dsl:1.0.9")
18 | ```
19 |
20 | ### Maven
21 |
22 | ```xml
23 |
24 | com.symbaloo
25 | graphql-kotlin-test-dsl
26 | 1.0.9
27 |
28 | ```
29 |
30 | Example
31 | -------
32 |
33 | The following code shows an example how to use this test library.
34 |
35 | ```kotlin
36 | // the graphql-java schema
37 | val schema: GraphQL = createTestSchema()
38 | val result: ExecutionResult = graphQLTest(schema) {
39 | // define a query
40 | query(
41 | """
42 | |query Init(${"$"}echo: String) {
43 | | echo(echo: ${"$"}echo)
44 | | hello { hello }
45 | |}""".trimMargin()
46 | )
47 | // add a variable
48 | variable("echo", "response")
49 | }
50 | // check for noErrors
51 | .andExpect { noErrors() }
52 | // create json context
53 | .andExpectJson {
54 | // go into the result with a json path
55 | path("$.hello.hello") {
56 | // quick isEqualTo check
57 | isEqualTo("world")
58 | // do something with the result
59 | andDo {
60 | assertThat(it).isEqualTo("world")
61 | }
62 | }
63 | // combination of `path` and `andDo`
64 | pathAndDo("$.hello") { it: Map ->
65 | assertThat(it).contains("hello", "world")
66 | }
67 |
68 | // combination of `path` and `isEqualTo`
69 | pathIsEqualTo("$.echo", "response")
70 |
71 | // it can also return values
72 | val hello = pathAndDo("$.hello") { map: Map ->
73 | map["hello"]
74 | }
75 | assertThat(hello).isEqualTo("world")
76 | }
77 | .andReturn()
78 | ```
79 |
80 | ### Link with your Assertion Library
81 |
82 | ```kotlin
83 | graphQLTest(createTestSchema()) {
84 | query("{ answer }")
85 | }.andExpectJson {
86 | assertPath("$.answer").isEqualTo(42)
87 | }
88 |
89 | fun GraphQLJsonResultMatcherDsl.assertPath(path: String): Assert =
90 | pathAndDo(path) { it: Any? -> assertThat(it) }
91 | ```
92 |
93 | License
94 | -------
95 |
96 | MIT License
97 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | kotlin("jvm") version "1.4.21"
5 | id("com.diffplug.spotless") version "5.9.0"
6 | id("org.jetbrains.dokka") version "1.4.20"
7 | `java-library`
8 | `maven-publish`
9 | signing
10 | }
11 |
12 | group = "com.symbaloo"
13 | version = "1.0.9"
14 | description = "A Kotlin DSL to write Tests for graphql-java"
15 | val repoDescription = description
16 | val repoUrl = "https://github.com/arian/graphql-kotlin-test-dsl"
17 |
18 | val isReleaseVersion = !version.toString().endsWith("SNAPSHOT")
19 |
20 | repositories {
21 | mavenCentral()
22 | jcenter()
23 | }
24 |
25 | dependencies {
26 | api("com.graphql-java:graphql-java:15.+")
27 | implementation("com.jayway.jsonpath:json-path:2.5.+")
28 | implementation("com.google.code.gson:gson:2.8.6")
29 | implementation("org.opentest4j:opentest4j:1.2.0")
30 | implementation("org.slf4j:slf4j-api:1.7.+")
31 | testImplementation(kotlin("reflect"))
32 | testImplementation("org.slf4j:slf4j-simple:1.7.+")
33 | testImplementation("org.junit.jupiter:junit-jupiter:5.7.0")
34 | testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.23")
35 | }
36 |
37 | tasks {
38 | withType {
39 | kotlinOptions.jvmTarget = "1.8"
40 | }
41 |
42 | test {
43 | useJUnitPlatform()
44 | testLogging {
45 | events("passed", "skipped", "failed")
46 | }
47 | }
48 |
49 | register("sourcesJar") {
50 | archiveClassifier.set("sources")
51 | from(sourceSets.main.get().allSource)
52 | }
53 |
54 | javadoc {
55 | dependsOn("dokkaJavadoc")
56 | }
57 |
58 | register("javadocJar") {
59 | dependsOn("javadoc")
60 | archiveClassifier.set("javadoc")
61 | from("$buildDir/javadoc")
62 | }
63 | }
64 |
65 | spotless {
66 | kotlin {
67 | ktlint("0.40.0")
68 | }
69 | kotlinGradle {
70 | ktlint("0.40.0")
71 | }
72 | }
73 |
74 | publishing {
75 | publications {
76 | create("mavenJava") {
77 | from(components["kotlin"])
78 | artifact(tasks["sourcesJar"])
79 | artifact(tasks["javadocJar"])
80 |
81 | versionMapping {
82 | usage("java-api") {
83 | fromResolutionOf("runtimeClasspath")
84 | }
85 | usage("java-runtime") {
86 | fromResolutionResult()
87 | }
88 | }
89 |
90 | pom {
91 | name.set("GraphQL Kotlin Test DLS")
92 | description.set(repoDescription)
93 | url.set(repoUrl)
94 |
95 | licenses {
96 | license {
97 | name.set("MIT")
98 | url.set("http://www.opensource.org/licenses/mit-license.php")
99 | distribution.set("repo")
100 | }
101 | }
102 |
103 | developers {
104 | developer {
105 | id.set("arian")
106 | name.set("Arian Stolwijk")
107 | email.set("arian@symbaloo.com")
108 | }
109 | }
110 |
111 | scm {
112 | connection.set("scm:git:git://github.com/arian/graphql-kotlin-test-dsl.git")
113 | url.set("https://github.com/arian/graphql-kotlin-test-dsl")
114 | }
115 | }
116 | }
117 |
118 | repositories {
119 | maven {
120 |
121 | val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
122 | val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots")
123 | url = if (isReleaseVersion) releasesRepoUrl else snapshotsRepoUrl
124 |
125 | // these can be set through gradle.properties
126 | if (properties.containsKey("mavenRepoUser")) {
127 | credentials {
128 | username = properties["sonatypeRepoUser"] as String?
129 | password = properties["sonatypeRepoPassword"] as String?
130 | }
131 | }
132 | }
133 | }
134 | }
135 | }
136 |
137 | signing {
138 | setRequired { isReleaseVersion && gradle.taskGraph.hasTask("publish") }
139 | if (isReleaseVersion) {
140 | sign(publishing.publications["mavenJava"])
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/example.gradle.properties:
--------------------------------------------------------------------------------
1 | # https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials
2 | signing.keyId=abc
3 | signing.password=secret
4 | signing.secretKeyRingFile=/home/foobar/.gnupg/secring.gpg
5 |
6 | # https://central.sonatype.org/pages/gradle.html
7 | mavenRepoUser=user
8 | mavenRepoPassword=secret
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arian/graphql-kotlin-test-dsl/bbe712eb0b36b00dc19e36a4ada2f33a48ea9e52/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/symbaloo/graphql/test/GraphQLKotlinTestDsl.kt:
--------------------------------------------------------------------------------
1 | package com.symbaloo.graphql.test
2 |
3 | import com.google.gson.GsonBuilder
4 | import com.jayway.jsonpath.JsonPath
5 | import com.jayway.jsonpath.PathNotFoundException
6 | import graphql.ExecutionInput
7 | import graphql.ExecutionResult
8 | import graphql.GraphQL
9 | import graphql.GraphQLError
10 | import org.opentest4j.AssertionFailedError
11 |
12 | /**
13 | * Initial function to kick of the test DSL with the GraphQL schema object
14 | */
15 | fun graphQLTest(schema: GraphQL, tester: GraphQLQueryBuilderDsl.() -> Unit): GraphQLResultActionsDsl {
16 | return GraphQLQueryBuilderDsl(schema).apply(tester).execute()
17 | }
18 |
19 | class GraphQLQueryBuilderDsl(
20 | private val schema: GraphQL,
21 | internal var query: String = ""
22 | ) {
23 |
24 | internal val variables = mutableMapOf()
25 | internal var context: Any? = null
26 | internal var builder: ((ExecutionInput.Builder) -> Unit)? = null
27 |
28 | internal fun execute(): GraphQLResultActionsDsl {
29 | val executionResult = schema.execute {
30 | it.query(query)
31 | variables.takeIf { v -> v.isNotEmpty() }?.also { v -> it.variables(v) }
32 | context?.also { c -> it.context(c) }
33 | builder?.also { b -> b(it) }
34 | it
35 | }
36 | return GraphQLResultActionsDsl(executionResult)
37 | }
38 | }
39 |
40 | /**
41 | * Set the query to test
42 | */
43 | fun GraphQLQueryBuilderDsl.query(query: String) {
44 | this.query = query
45 | }
46 |
47 | /**
48 | * Read .graphql query files from (test) resources
49 | */
50 | fun GraphQLQueryBuilderDsl.queryFromFile(filename: String) {
51 | query(
52 | Thread.currentThread().contextClassLoader
53 | ?.getResourceAsStream(filename)
54 | .let { requireNotNull(it) }
55 | .use { String(it.readBytes()) }
56 | )
57 | }
58 |
59 | /**
60 | * Add a variable
61 | */
62 | fun GraphQLQueryBuilderDsl.variable(name: String, value: Any?) {
63 | this.variables[name] = value
64 | }
65 |
66 | /**
67 | * Add multiple variables
68 | */
69 | fun GraphQLQueryBuilderDsl.variables(map: Map) {
70 | this.variables += map
71 | }
72 |
73 | /**
74 | * Set a context object
75 | */
76 | fun GraphQLQueryBuilderDsl.context(context: Any) {
77 | this.context = context
78 | }
79 |
80 | /**
81 | * Use the graphql-java builder to set query inputs
82 | * @see ExecutionInput.Builder
83 | */
84 | fun GraphQLQueryBuilderDsl.builder(builder: (ExecutionInput.Builder) -> Unit) {
85 | this.builder = builder
86 | }
87 |
88 | class GraphQLResultActionsDsl(internal val executionResult: ExecutionResult)
89 |
90 | /**
91 | * @return [GraphQLResultActionsDsl] for asserting and checking results
92 | */
93 | fun GraphQLResultActionsDsl.andExpect(expectations: GraphQLResultMatcherDsl.() -> Unit): GraphQLResultActionsDsl {
94 | GraphQLResultMatcherDsl(executionResult).expectations()
95 | return this
96 | }
97 |
98 | /**
99 | * @return [GraphQLResultActionsDsl] for asserting and checking results
100 | */
101 | fun GraphQLResultActionsDsl.andExpectJson(
102 | expectations: GraphQLJsonResultMatcherDsl.() -> Unit
103 | ): GraphQLResultActionsDsl {
104 | andExpect { json { expectations() } }
105 | return this
106 | }
107 |
108 | /**
109 | * Do something with the executing result
110 | * @see ExecutionResult
111 | */
112 | fun GraphQLResultActionsDsl.andDo(action: (ExecutionResult) -> Unit): GraphQLResultActionsDsl {
113 | action(executionResult)
114 | return this
115 | }
116 |
117 | /**
118 | * Return the execution result
119 | * @see ExecutionResult
120 | */
121 | fun GraphQLResultActionsDsl.andReturn(): ExecutionResult {
122 | return executionResult
123 | }
124 |
125 | /**
126 | * Return the data at a given path
127 | * @see ExecutionResult
128 | * @see GraphQLResultMatcherDsl.path
129 | */
130 | fun GraphQLResultActionsDsl.andReturnPath(path: String): T {
131 | return JsonPathContext(executionResult.getData()).readJsonPathOrFail(path)
132 | }
133 |
134 | class GraphQLResultMatcherDsl(internal val executionResult: ExecutionResult)
135 |
136 | /**
137 | * Assert that there are no errors in the result
138 | */
139 | fun GraphQLResultMatcherDsl.noErrors() {
140 | val errors = this.executionResult.errors
141 | if (errors.isNotEmpty()) {
142 | fail(
143 | """
144 | |Expected no errors in the result.
145 | |
146 | |It got these errors:
147 | |
148 | |${errors.joinToString(">\n>\n") { it.message.prependIndent(">> ") }}
149 | |""".trimMargin(),
150 | emptyList(),
151 | errors
152 | )
153 | }
154 | }
155 |
156 | /**
157 | * Assert that there is at least one error
158 | */
159 | fun GraphQLResultMatcherDsl.hasError(matcher: GraphQLErrorResultMatcher.() -> Unit = { }) {
160 | val errors = this.executionResult.errors
161 | if (errors.isEmpty()) {
162 | fail(
163 | """
164 | |Expected at least one error in the result. But there were no errors.
165 | |""".trimMargin(),
166 | null,
167 | errors
168 | )
169 | } else {
170 | GraphQLErrorResultMatcher(errors).matcher()
171 | }
172 | }
173 |
174 | /**
175 | * Assert that some field has some value
176 | */
177 | fun GraphQLResultMatcherDsl.rootFieldEqualTo(key: String, expected: T) {
178 | when (val data = executionResult.getData()) {
179 | is Map<*, *> -> {
180 | val actual = data[key]
181 | if (actual != expected) {
182 | fail("Expected field with key: $key", expected, actual)
183 | }
184 | }
185 | else -> {
186 | throw AssertionFailedError("Expected root data to be a map and contain field(s)")
187 | }
188 | }
189 | }
190 |
191 | /**
192 | * Parse the [ExecutionResult] data into a [GraphQLJsonResultMatcherDsl] DSL
193 | */
194 | fun GraphQLResultMatcherDsl.json(fn: GraphQLJsonResultMatcherDsl.() -> R): R {
195 | val context = JsonPathContext(executionResult.getData())
196 | return GraphQLJsonResultMatcherDsl(context).fn()
197 | }
198 |
199 | /**
200 | * Get a [GraphQLJsonPathResultMatcherDsl] DSL for a certain [JsonPath] path
201 | */
202 | fun GraphQLResultMatcherDsl.path(jsonPath: String, fn: GraphQLJsonPathResultMatcherDsl.() -> R): R {
203 | return json { path(jsonPath, fn) }
204 | }
205 |
206 | fun GraphQLResultMatcherDsl.path(jsonPath: String, fn: GraphQLJsonPathResultMatcherDsl.() -> Unit): Unit =
207 | path(jsonPath, fn)
208 |
209 | /**
210 | * Test a value in the response
211 | */
212 | fun GraphQLResultMatcherDsl.pathIsEqualTo(path: String, value: T) {
213 | return json { path(path) { isEqualTo(value) } }
214 | }
215 |
216 | /**
217 | * Be able to do something (e.g. assertions) with the value at the given path
218 | */
219 | fun GraphQLResultMatcherDsl.pathAndDo(path: String, matcher: (T) -> R): R {
220 | return json { path(path) { andDo(matcher) } }
221 | }
222 |
223 | class GraphQLJsonResultMatcherDsl internal constructor(internal val context: JsonPathContext)
224 |
225 | /**
226 | * Use the [GraphQLJsonPathResultMatcherDsl] for a certain [JsonPath] path
227 | */
228 | fun GraphQLJsonResultMatcherDsl.path(path: String, fn: GraphQLJsonPathResultMatcherDsl.() -> R): R {
229 | val value: T = context.readJsonPathOrFail(path)
230 | return GraphQLJsonPathResultMatcherDsl(path, context.data, value).fn()
231 | }
232 |
233 | fun GraphQLJsonResultMatcherDsl.path(path: String, fn: GraphQLJsonPathResultMatcherDsl.() -> Unit): Unit =
234 | path(path, fn)
235 |
236 | /**
237 | * Test the result of a [JsonPath] to be equal to a certain value
238 | */
239 | fun GraphQLJsonResultMatcherDsl.pathIsEqualTo(path: String, expected: T): Unit =
240 | path(path) { isEqualTo(expected) }
241 |
242 | /**
243 | * Be able to do something (e.g. assertions) with the value at the given path
244 | */
245 | fun GraphQLJsonResultMatcherDsl.pathAndDo(path: String, matcher: (T) -> R): R {
246 | return path(path) { andDo(matcher) }
247 | }
248 |
249 | /**
250 | * Returns the [ExecutionResult] as a JSON string
251 | */
252 | fun GraphQLJsonResultMatcherDsl.doWithJsonString(fn: (String) -> R): R {
253 | return fn(context.json)
254 | }
255 |
256 | class GraphQLJsonPathResultMatcherDsl(internal val path: String, internal val data: Any, internal val value: T)
257 |
258 | /**
259 | * Do something with the result
260 | */
261 | fun GraphQLJsonPathResultMatcherDsl.andDo(matcher: (T) -> R): R {
262 | return matcher(value)
263 | }
264 |
265 | fun GraphQLJsonPathResultMatcherDsl.isEqualTo(expected: T) {
266 | if (expected != value) {
267 | fail(
268 | """
269 | |
270 | |Expected <$value> to be equal to <$expected>
271 | |
272 | |In path: $path
273 | |
274 | |In data: $data
275 | |
276 | """.trimMargin(),
277 | expected,
278 | value
279 | )
280 | }
281 | }
282 |
283 | internal class JsonPathContext(
284 | internal val data: Any
285 | ) {
286 | internal val json = GsonBuilder().serializeNulls().create().toJson(data)
287 | private val context = JsonPath.parse(json)
288 |
289 | fun readJsonPathOrFail(path: String): T {
290 | return try {
291 | context.read(path)
292 | } catch (e: PathNotFoundException) {
293 | throw AssertionError("No results for path: $path\n\nIn data: $data")
294 | }
295 | }
296 | }
297 |
298 | class GraphQLErrorResultMatcher(internal val errors: List)
299 |
300 | fun GraphQLErrorResultMatcher.path(path: String, matcher: (GraphQLError) -> Unit = { }) {
301 | val parts = path.split(".")
302 | when (val error = errors.find { it.path == parts }) {
303 | null -> fail("Error with path '$path' couldn't be found", path, errors)
304 | else -> matcher(error)
305 | }
306 | }
307 |
308 | private fun fail(message: String, expected: Any?, value: Any?) {
309 | throw AssertionFailedError(
310 | "expected: <$expected> but was: <$value>",
311 | expected,
312 | value,
313 | AssertionError(message)
314 | )
315 | }
316 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/symbaloo/graphql/test/GraphQLKotlinTestDslTest.kt:
--------------------------------------------------------------------------------
1 | package com.symbaloo.graphql.test
2 |
3 | import assertk.Assert
4 | import assertk.all
5 | import assertk.assertThat
6 | import assertk.assertions.contains
7 | import assertk.assertions.hasSize
8 | import assertk.assertions.isEqualTo
9 | import assertk.assertions.isFailure
10 | import assertk.assertions.isInstanceOf
11 | import assertk.assertions.isNotNull
12 | import assertk.assertions.isNull
13 | import assertk.assertions.key
14 | import assertk.assertions.prop
15 | import assertk.assertions.startsWith
16 | import graphql.ExceptionWhileDataFetching
17 | import graphql.ExecutionResult
18 | import graphql.GraphQL
19 | import graphql.schema.idl.RuntimeWiring.newRuntimeWiring
20 | import graphql.schema.idl.SchemaGenerator
21 | import graphql.schema.idl.SchemaParser
22 | import org.junit.jupiter.api.DisplayName
23 | import org.junit.jupiter.api.Nested
24 | import org.junit.jupiter.api.Test
25 | import org.junit.jupiter.api.assertThrows
26 | import org.opentest4j.AssertionFailedError
27 |
28 | private data class Bar(val foo: String)
29 |
30 | private val schema = """
31 | |type Query {
32 | | hello: Bar
33 | | answer: Int
34 | | echo(echo: String): String
35 | | fromContext: String
36 | | list: [Bar!]!
37 | | error: Int
38 | |}
39 | |type Bar {
40 | | hello: String
41 | | infinite: Bar
42 | |}
43 | |""".trimMargin()
44 |
45 | class GraphQLKotlinTestDslTest {
46 |
47 | private fun createTestSchema(): GraphQL {
48 | val schemaParser = SchemaParser()
49 | val typeDefinitionRegistry = schemaParser.parse(schema)
50 | val runtimeWiring = newRuntimeWiring()
51 | .type("Query") { builder ->
52 | builder.dataFetcher("hello") { Bar("world") }
53 | builder.dataFetcher("answer") { 42 }
54 | builder.dataFetcher("echo") { it.arguments["echo"] }
55 | builder.dataFetcher("fromContext") { it.getContext().foo }
56 | builder.dataFetcher("list") { listOf(Bar("first"), Bar("second")) }
57 | builder.dataFetcher("error") { throw Exception("oops") }
58 | }
59 | .type("Bar") { builder ->
60 | builder.dataFetcher("hello") { it.getSource().foo }
61 | builder.dataFetcher("infinite") {
62 | val bar = it.getSource()
63 | bar.copy(foo = bar.foo.repeat(2))
64 | }
65 | }
66 | .build()
67 |
68 | val schemaGenerator = SchemaGenerator()
69 | val graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring)
70 | return GraphQL.newGraphQL(graphQLSchema).build()
71 | }
72 |
73 | @Test
74 | fun `everything is fine`() {
75 | // the graphql-java schema
76 | val schema: GraphQL = createTestSchema()
77 | val result = graphQLTest(schema) {
78 | // define a query
79 | query(
80 | """
81 | |query Init(${"$"}echo: String) {
82 | | echo(echo: ${"$"}echo)
83 | | hello { hello }
84 | |}""".trimMargin()
85 | )
86 | // add a variable
87 | variable("echo", "response")
88 | }
89 | // check for noErrors
90 | .andExpect { noErrors() }
91 | // create json context
92 | .andExpectJson {
93 | // go into the result with a json path
94 | path("$.hello.hello") {
95 | // quick isEqualTo check
96 | isEqualTo("world")
97 | // do something with the result
98 | andDo {
99 | assertThat(it).isEqualTo("world")
100 | }
101 | }
102 | // combination of `path` and `andDo`
103 | pathAndDo("$.hello") { it: Map ->
104 | assertThat(it).contains("hello", "world")
105 | }
106 |
107 | // combination of `path` and `isEqualTo`
108 | pathIsEqualTo("$.echo", "response")
109 |
110 | // it can also return values
111 | val hello = pathAndDo("$.hello") { map: Map ->
112 | map["hello"]
113 | }
114 | assertThat(hello).isEqualTo("world")
115 | }
116 | .andReturn()
117 |
118 | assertThat(result.extensions).isNull()
119 | }
120 |
121 | @Test
122 | fun `assert library`() {
123 | graphQLTest(createTestSchema()) {
124 | query("{ answer }")
125 | }.andExpectJson {
126 | assertPath("$.answer").isEqualTo(42)
127 | }
128 | }
129 |
130 | @Nested
131 | inner class GraphQLQueryBuilderDsl {
132 |
133 | @Test
134 | fun `variable dsl`() {
135 | graphQLTest(createTestSchema()) {
136 | query("query X(\$echo: String) { echo(echo: \$echo) }")
137 | variable("echo", "world")
138 | }.andExpect {
139 | noErrors()
140 | pathIsEqualTo("\$.echo", "world")
141 | }
142 | }
143 |
144 | @Test
145 | fun `variables dsl`() {
146 | val q = """
147 | |query X($${"echo"}: String, $${"e2"}: String) {
148 | | echo: echo(echo: $${"echo"})
149 | | e2: echo(echo: $${"e2"})
150 | |}""".trimMargin()
151 | graphQLTest(createTestSchema()) {
152 | query(q)
153 | variables(mapOf("echo" to "world", "e2" to "second"))
154 | }.andExpect {
155 | noErrors()
156 | pathIsEqualTo("\$.echo", "world")
157 | pathIsEqualTo("\$.e2", "second")
158 | }
159 | }
160 |
161 | @Test
162 | fun `context dsl`() {
163 | graphQLTest(createTestSchema()) {
164 | query("{ fromContext }")
165 | context(Bar("hey"))
166 | }.andExpect {
167 | noErrors()
168 | rootFieldEqualTo("fromContext", "hey")
169 | }
170 | }
171 |
172 | @Test
173 | fun `builder dsl`() {
174 | graphQLTest(createTestSchema()) {
175 | query("query X(\$echo: String) { fromContext echo(echo: \$echo) }")
176 | builder { input ->
177 | input.context(Bar("ctx"))
178 | input.variables(mapOf("echo" to "abc"))
179 | }
180 | }.andExpect {
181 | noErrors()
182 | rootFieldEqualTo("echo", "abc")
183 | rootFieldEqualTo("fromContext", "ctx")
184 | }
185 | }
186 |
187 | @Test
188 | fun `queryFromFile dsl`() {
189 | graphQLTest(createTestSchema()) {
190 | queryFromFile("./query.graphql")
191 | variable("echo", "abc")
192 | }.andExpect {
193 | noErrors()
194 | }
195 | }
196 |
197 | @Test
198 | fun `queryFromFile dsl can't find the file`() {
199 | assertThrows {
200 | graphQLTest(createTestSchema()) {
201 | queryFromFile("not-existing.graphql")
202 | variable("echo", "abc")
203 | }
204 | }
205 | }
206 | }
207 |
208 | @Nested
209 | @DisplayName("expect field to equal")
210 | inner class GraphQLResultActionsDsl {
211 |
212 | @Test
213 | fun andExpect() {
214 | graphQLTest(createTestSchema()) {
215 | query("{ answer }")
216 | }.andExpect {
217 | rootFieldEqualTo("answer", 42)
218 | }
219 | }
220 |
221 | @Test
222 | fun andDo() {
223 | graphQLTest(createTestSchema()) {
224 | query("{ answer }")
225 | }.andDo {
226 | assertThat(it.errors).hasSize(0)
227 | }
228 | }
229 |
230 | @Test
231 | fun `return execution result`() {
232 | val result = graphQLTest(createTestSchema()) {
233 | query("{ answer }")
234 | }.andReturn()
235 |
236 | assertThat(result).all {
237 | isInstanceOf(ExecutionResult::class.java)
238 | transform { it.getData