Skip to content

Commit 20fcb8d

Browse files
committed
chore: add field resolver generator tests
1 parent 1ec6135 commit 20fcb8d

File tree

4 files changed

+306
-2
lines changed

4 files changed

+306
-2
lines changed

graphql-kotlin-toolkit-codegen/src/test/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/_test/AbstractCompilationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import kotlin.reflect.KClass
77

88
abstract class AbstractCompilationTest {
99

10-
protected fun compile(generator: FileGenerator): KClass<*> {
10+
protected open fun compile(generator: FileGenerator): KClass<*> {
1111
// Generate the code of the generator.
1212
val fileSpec = generator.generate()
1313

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen._test
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.generator.FileGenerator
4+
import com.squareup.kotlinpoet.ClassName
5+
import com.squareup.kotlinpoet.FileSpec
6+
import com.squareup.kotlinpoet.KModifier
7+
import com.squareup.kotlinpoet.TypeSpec
8+
import com.tschuchort.compiletesting.KotlinCompilation
9+
import com.tschuchort.compiletesting.SourceFile
10+
import kotlin.reflect.KClass
11+
12+
abstract class AbstractMockCompilationTest : AbstractCompilationTest() {
13+
override fun compile(generator: FileGenerator): KClass<*> {
14+
val baseFileSpec = generator.generate()
15+
16+
val firstTypeSpec = baseFileSpec.members.filterIsInstance<TypeSpec>().firstOrNull()
17+
?: return super.compile(generator)
18+
19+
val mockImplementation = createImplementation(baseFileSpec, firstTypeSpec)
20+
21+
val generatedSource = SourceFile.kotlin("Generated.kt", baseFileSpec.toString())
22+
val mockSource = SourceFile.kotlin("Mock.kt", mockImplementation.toString())
23+
24+
val compilation = KotlinCompilation()
25+
26+
compilation.sources = listOf(generatedSource, mockSource)
27+
compilation.inheritClassPath = true
28+
29+
val compileResult = compilation.compile()
30+
31+
return compileResult.classLoader.loadClass(baseFileSpec.packageName + "." + baseFileSpec.name).kotlin
32+
}
33+
34+
private fun createImplementation(file: FileSpec, type: TypeSpec): FileSpec {
35+
val classToMock = ClassName(file.packageName, type.name!!)
36+
37+
return FileSpec.builder(TestObject.options.generatedBasePackage, "MockImplementation")
38+
.addType(
39+
TypeSpec.classBuilder("MockImplementation")
40+
.addSuperinterface(classToMock)
41+
.addFunctions(type.funSpecs
42+
.filter { KModifier.ABSTRACT in it.modifiers }
43+
.map {
44+
it.toBuilder()
45+
.addModifiers(KModifier.OVERRIDE)
46+
.addCode("TODO()")
47+
.also { t -> t.modifiers.remove(KModifier.ABSTRACT) }
48+
.build()
49+
})
50+
.build()
51+
)
52+
.build()
53+
}
54+
}

graphql-kotlin-toolkit-codegen/src/test/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/_test/TestObject.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import graphql.schema.GraphQLSchema
1010
import java.nio.file.Path
1111

1212
internal object TestObject {
13-
val options = CodegenOptions(hashSetOf(), Path.of(""))
13+
val options = CodegenOptions(hashSetOf(), Path.of(""), generateAll = false)
1414
val generatedMapper = GeneratedMapper(options)
1515
val kotlinTypeMapper = KotlinTypeMapper(options, generatedMapper)
1616
val argumentCodeBlockGenerator = ArgumentCodeBlockGenerator(kotlinTypeMapper, generatedMapper)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.generator.fieldResolver
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen._test.AbstractMockCompilationTest
4+
import com.auritylab.graphql.kotlin.toolkit.codegen._test.TestObject
5+
import graphql.Scalars
6+
import graphql.schema.GraphQLArgument
7+
import graphql.schema.GraphQLFieldDefinition
8+
import graphql.schema.GraphQLNonNull
9+
import graphql.schema.GraphQLObjectType
10+
import org.junit.jupiter.api.Assertions
11+
import org.junit.jupiter.api.BeforeAll
12+
import org.junit.jupiter.api.DisplayName
13+
import org.junit.jupiter.api.Nested
14+
import org.junit.jupiter.api.Test
15+
import org.junit.jupiter.api.TestInstance
16+
import kotlin.reflect.KClass
17+
import kotlin.reflect.KFunction
18+
import kotlin.reflect.KProperty1
19+
import kotlin.reflect.full.companionObject
20+
import kotlin.reflect.full.companionObjectInstance
21+
import kotlin.reflect.full.createType
22+
import kotlin.reflect.full.memberFunctions
23+
import kotlin.reflect.full.memberProperties
24+
import kotlin.reflect.full.starProjectedType
25+
26+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
27+
internal class FieldResolverGeneratorTest : AbstractMockCompilationTest() {
28+
@Nested
29+
@DisplayName("Simple field resolver")
30+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
31+
inner class SimpleFieldResolver {
32+
val generator = FieldResolverGenerator(
33+
testObjectType,
34+
testSimpleFieldDefinition,
35+
TestObject.implementerMapper,
36+
TestObject.argumentCodeBlockGenerator,
37+
TestObject.options,
38+
TestObject.kotlinTypeMapper,
39+
TestObject.generatedMapper
40+
)
41+
42+
lateinit var generatedClass: KClass<*>
43+
44+
@BeforeAll
45+
fun compileCode() {
46+
// Compile the code of the generator
47+
generatedClass = compile(generator)
48+
}
49+
50+
@Test
51+
fun `should generate meta properties correctly`() {
52+
val companionObject = generatedClass.companionObject
53+
val companionObjectInstance = generatedClass.companionObjectInstance
54+
Assertions.assertNotNull(companionObject)
55+
Assertions.assertNotNull(companionObjectInstance)
56+
57+
// Cast to not null.
58+
companionObject!!
59+
companionObjectInstance!!
60+
61+
// Assert against the meta properties.
62+
val companionProperties = companionObject.memberProperties
63+
Assertions.assertEquals(2, companionProperties.size)
64+
65+
// Assert against each property in the companion object.
66+
val metaContainerProperty =
67+
companionProperties.firstOrNull { it.name == "META_CONTAINER" } as? KProperty1<Any?, String>
68+
val metaFieldProperty =
69+
companionProperties.firstOrNull { it.name == "META_FIELD" } as? KProperty1<Any?, String>
70+
Assertions.assertNotNull(metaContainerProperty)
71+
Assertions.assertNotNull(metaFieldProperty)
72+
metaContainerProperty!!
73+
metaFieldProperty!!
74+
75+
Assertions.assertEquals(testObjectType.name, metaContainerProperty.call())
76+
Assertions.assertEquals(testSimpleFieldDefinition.name, metaFieldProperty.call())
77+
}
78+
79+
@Test
80+
fun `should generate environment holder correctly`() {
81+
// Search for a nested class with the name "Env".
82+
val firstEnvClass = getEnvClass(generatedClass)
83+
84+
// Assert against the member properties.
85+
val memberProperties = firstEnvClass.memberProperties
86+
Assertions.assertEquals(3, memberProperties.size)
87+
88+
Assertions.assertNotNull(memberProperties.firstOrNull { it.name == "original" })
89+
Assertions.assertNotNull(memberProperties.firstOrNull { it.name == "parent" })
90+
Assertions.assertNotNull(memberProperties.firstOrNull { it.name == "context" })
91+
}
92+
93+
@Test
94+
fun `should generate 'resolve' method correctly`() {
95+
val resolveFunction = getResolveFunction(generatedClass)
96+
97+
// Assert against the parameters of the resolve function.
98+
val functionParameters = resolveFunction.parameters
99+
Assertions.assertEquals(2, functionParameters.size) // 2 as the first parameter is the receiver.
100+
101+
// Assert that the type of the env parameter is the Env class of this resolver.
102+
val envParameter = functionParameters[1]
103+
Assertions.assertEquals(getEnvClass(generatedClass).starProjectedType, envParameter.type)
104+
105+
// Assert that the resolve function will return a 'String?'.
106+
Assertions.assertEquals(String::class.createType(nullable = true), resolveFunction.returnType)
107+
}
108+
109+
@Test
110+
fun `should override and implement the 'get' method correctly`() {
111+
val getFunction = generatedClass.memberFunctions.firstOrNull { it.name == "get" }
112+
Assertions.assertNotNull(getFunction)
113+
getFunction!!
114+
115+
// Assert against the parameters of the get function.
116+
val functionParameters = getFunction.parameters
117+
Assertions.assertEquals(2, functionParameters.size) // 2 as the first parameter is the receiver.
118+
119+
//Assert that the get function will return a 'String?'.
120+
Assertions.assertEquals(String::class.createType(nullable = true), getFunction.returnType)
121+
}
122+
}
123+
124+
@Nested
125+
@DisplayName("Argument field resolver")
126+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
127+
inner class ArgumentFieldResolver {
128+
val generator = FieldResolverGenerator(
129+
testObjectType,
130+
testArgumentFieldDefinition,
131+
TestObject.implementerMapper,
132+
TestObject.argumentCodeBlockGenerator,
133+
TestObject.options,
134+
TestObject.kotlinTypeMapper,
135+
TestObject.generatedMapper
136+
)
137+
138+
lateinit var generatedClass: KClass<*>
139+
140+
@BeforeAll
141+
fun compileCode() {
142+
// Compile the code of the generator
143+
generatedClass = compile(generator)
144+
}
145+
146+
@Test
147+
fun `should generate parameter correctly`() {
148+
val resolveFunction = getResolveFunction(generatedClass)
149+
150+
// Assert against the function parameters.
151+
val functionParameters = resolveFunction.parameters
152+
Assertions.assertEquals(4, functionParameters.size)
153+
154+
// Assert against the additional parameters.
155+
val boolParameter = functionParameters.firstOrNull { it.name == "bool"}
156+
val intParameter = functionParameters.firstOrNull { it.name == "int"}
157+
Assertions.assertNotNull(boolParameter)
158+
Assertions.assertNotNull(intParameter)
159+
boolParameter!!
160+
intParameter!!
161+
162+
Assertions.assertEquals(Boolean::class.starProjectedType, boolParameter.type)
163+
Assertions.assertEquals(Int::class.starProjectedType, intParameter.type)
164+
}
165+
166+
@Test
167+
fun `should generate return type correctly` () {
168+
val resolveFunction = getResolveFunction(generatedClass)
169+
170+
val returnType = resolveFunction.returnType
171+
172+
Assertions.assertEquals(String::class.starProjectedType, returnType)
173+
}
174+
}
175+
176+
@Nested
177+
@DisplayName("Global context options")
178+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
179+
inner class GlobalContextOptions {
180+
val generator = FieldResolverGenerator(
181+
testObjectType,
182+
testArgumentFieldDefinition,
183+
TestObject.implementerMapper,
184+
TestObject.argumentCodeBlockGenerator,
185+
TestObject.options.copy(globalContext = "kotlin.String"),
186+
TestObject.kotlinTypeMapper,
187+
TestObject.generatedMapper
188+
)
189+
190+
lateinit var generatedClass: KClass<*>
191+
192+
@BeforeAll
193+
fun compileCode() {
194+
// Compile the code of the generator
195+
generatedClass = compile(generator)
196+
}
197+
198+
@Test
199+
fun `should apply custom global context correctly` () {
200+
val envClass = getEnvClass(generatedClass)
201+
202+
// Assert against the properties one more time
203+
val memberProperties = envClass.memberProperties
204+
Assertions.assertEquals(3, memberProperties.size)
205+
206+
// Search for the "context" property.
207+
val contextProperty = memberProperties.firstOrNull { it.name == "context" }
208+
Assertions.assertNotNull(contextProperty)
209+
contextProperty!!
210+
211+
Assertions.assertEquals(String::class.starProjectedType, contextProperty.returnType)
212+
}
213+
}
214+
215+
/**
216+
* Will return the "Env" class from the current [generatedClass].
217+
*/
218+
private fun getEnvClass(generated: KClass<*>): KClass<*> {
219+
val first = generated.nestedClasses.firstOrNull { it.simpleName == "Env" }
220+
Assertions.assertNotNull(first)
221+
return first!!
222+
}
223+
224+
/**
225+
* Will return the "resolve" function from the current [generatedClass].
226+
*/
227+
private fun getResolveFunction(generated: KClass<*>): KFunction<*> {
228+
val first = generated.memberFunctions.firstOrNull { it.name == "resolve" }
229+
Assertions.assertNotNull(first)
230+
return first!!
231+
}
232+
}
233+
234+
val testSimpleFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
235+
.name("simple")
236+
.type(Scalars.GraphQLString)
237+
.build()
238+
239+
val testArgumentFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
240+
.name("arguments")
241+
.argument(GraphQLArgument.newArgument().name("bool").type(GraphQLNonNull(Scalars.GraphQLBoolean)).build())
242+
.argument(GraphQLArgument.newArgument().name("int").type(GraphQLNonNull(Scalars.GraphQLInt)).build())
243+
.type(GraphQLNonNull(Scalars.GraphQLString))
244+
.build()
245+
246+
val testObjectType = GraphQLObjectType.newObject()
247+
.name("TestObjectType")
248+
.field(testSimpleFieldDefinition)
249+
.field(testArgumentFieldDefinition)
250+
.build()

0 commit comments

Comments
 (0)