Skip to content

Commit 2cb35b5

Browse files
committed
test(codegen): refactored AbstractCompilationTest and add FieldResolverGenerator test which checks on clashes
1 parent 5e1f825 commit 2cb35b5

File tree

2 files changed

+152
-46
lines changed

2 files changed

+152
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,83 @@
11
package com.auritylab.graphql.kotlin.toolkit.codegen._test
22

33
import com.auritylab.graphql.kotlin.toolkit.codegen.generator.FileGenerator
4+
import com.squareup.kotlinpoet.ClassName
45
import com.squareup.kotlinpoet.FileSpec
6+
import com.squareup.kotlinpoet.KModifier
7+
import com.squareup.kotlinpoet.TypeSpec
58
import com.tschuchort.compiletesting.KotlinCompilation
69
import com.tschuchort.compiletesting.SourceFile
710
import kotlin.reflect.KClass
811

9-
abstract class AbstractCompilationTest {
12+
abstract class AbstractCompilationTest(
13+
private val createInterfaceMocks: Boolean = false
14+
) {
15+
protected open fun compile(generator: FileGenerator) = internalCompile(generator.generate())
16+
protected open fun compile(fileSpec: FileSpec) = internalCompile(fileSpec)
17+
protected open fun compile(main: FileSpec, vararg dependencies: FileSpec) = internalCompile(main, *dependencies)
18+
protected open fun compile(main: FileGenerator, vararg dependencies: FileGenerator) =
19+
internalCompile(main.generate(), *dependencies.map { it.generate() }.toTypedArray())
1020

11-
protected open fun compile(generator: FileGenerator): KClass<*> {
12-
// Generate the code of the generator.
13-
val fileSpec = generator.generate()
21+
/**
22+
* Will compile the given [main] and the given [dependencies]. This function will return the runtime reflection
23+
* reference to the [main] class. The [dependencies] will just be added to the compilation.
24+
*/
25+
private fun internalCompile(main: FileSpec, vararg dependencies: FileSpec): KClass<*> {
26+
// Generate source files for the given file specs.
27+
val mainSource = buildSourceFile(main, 0)
28+
val dependencySources = dependencies.mapIndexed() { index, dep -> buildSourceFile(dep, index + 1) }
1429

15-
// Create the source files
16-
val source = SourceFile.kotlin("Generated.kt", fileSpec.toString())
30+
// Configure the compilation and run the compiler.
31+
val result = KotlinCompilation().apply {
32+
/// Add the generated sources to the compilation.
33+
sources = listOf(mainSource, *dependencySources.toTypedArray())
34+
inheritClassPath = true
35+
}.compile()
1736

18-
val compilation = KotlinCompilation()
37+
// Throw an exception if the compilation exited with an unsuccessful exit code.
38+
if (result.exitCode != KotlinCompilation.ExitCode.OK)
39+
throw IllegalStateException("Kotlin compilation not successful!")
1940

20-
compilation.sources = listOf(source)
21-
compilation.inheritClassPath = false
41+
// Load the given main class using the ClassLoader.
42+
return result.classLoader.loadClass(main.packageName + "." + main.name).kotlin
43+
}
2244

23-
val compileResult = compilation.compile()
45+
/**
46+
* Will build the [SourceFile] based on the given [spec].
47+
* This will use the name of the class and append the ".kt" extension.
48+
*/
49+
private fun buildSourceFile(spec: FileSpec, counter: Int): SourceFile {
50+
val file: FileSpec = if (createInterfaceMocks) {
51+
// Search on the members of the given file spec for types of kind interface.
52+
val possibleInterfaces = spec.members
53+
.filterIsInstance<TypeSpec>()
54+
.filter { it.kind == TypeSpec.Kind.INTERFACE }
2455

25-
return compileResult.classLoader.loadClass(fileSpec.packageName + "." + fileSpec.name).kotlin
26-
}
56+
val builder = spec.toBuilder()
2757

28-
protected open fun compile(fileSpec: FileSpec): KClass<*> {
29-
val source = SourceFile.kotlin("Generated.kt", fileSpec.toString())
58+
possibleInterfaces
59+
.forEach { type -> builder.addType(buildMockImplementation(spec, type)) }
3060

31-
val compilation = KotlinCompilation()
61+
builder.build()
62+
} else
63+
spec
3264

33-
compilation.sources = listOf(source)
34-
compilation.inheritClassPath = false
65+
return SourceFile.kotlin(spec.name + counter + ".kt", file.toString())
66+
}
3567

36-
val compileResult = compilation.compile()
68+
private fun buildMockImplementation(file: FileSpec, spec: TypeSpec): TypeSpec {
69+
val classToMock = ClassName(file.packageName, spec.name!!)
3770

38-
return compileResult.classLoader.loadClass(fileSpec.packageName + "." + fileSpec.name).kotlin
71+
return TypeSpec.classBuilder(spec.name!! + "Mock")
72+
.addSuperinterface(classToMock)
73+
.addFunctions(spec.funSpecs
74+
.filter { KModifier.ABSTRACT in it.modifiers }
75+
.map {
76+
it.toBuilder()
77+
.addModifiers(KModifier.OVERRIDE)
78+
.addCode("TODO()")
79+
.also { t -> t.modifiers.remove(KModifier.ABSTRACT) }
80+
.build()
81+
}).build()
3982
}
4083
}

graphql-kotlin-toolkit-codegen/src/test/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/generator/fieldResolver/FieldResolverGeneratorTest.kt

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package com.auritylab.graphql.kotlin.toolkit.codegen.generator.fieldResolver
22

3-
import com.auritylab.graphql.kotlin.toolkit.codegen._test.AbstractMockCompilationTest
3+
import com.auritylab.graphql.kotlin.toolkit.codegen._test.AbstractCompilationTest
44
import com.auritylab.graphql.kotlin.toolkit.codegen._test.TestObject
55
import graphql.Scalars
66
import graphql.schema.GraphQLArgument
77
import graphql.schema.GraphQLFieldDefinition
8+
import graphql.schema.GraphQLFieldsContainer
89
import graphql.schema.GraphQLNonNull
910
import graphql.schema.GraphQLObjectType
11+
import org.junit.jupiter.api.Assertions
12+
import org.junit.jupiter.api.BeforeAll
13+
import org.junit.jupiter.api.DisplayName
14+
import org.junit.jupiter.api.Nested
15+
import org.junit.jupiter.api.Test
16+
import org.junit.jupiter.api.TestInstance
1017
import kotlin.reflect.KClass
1118
import kotlin.reflect.KFunction
1219
import kotlin.reflect.KProperty1
@@ -16,15 +23,9 @@ import kotlin.reflect.full.createType
1623
import kotlin.reflect.full.memberFunctions
1724
import kotlin.reflect.full.memberProperties
1825
import kotlin.reflect.full.starProjectedType
19-
import org.junit.jupiter.api.Assertions
20-
import org.junit.jupiter.api.BeforeAll
21-
import org.junit.jupiter.api.DisplayName
22-
import org.junit.jupiter.api.Nested
23-
import org.junit.jupiter.api.Test
24-
import org.junit.jupiter.api.TestInstance
2526

2627
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
27-
internal class FieldResolverGeneratorTest : AbstractMockCompilationTest() {
28+
internal class FieldResolverGeneratorTest : AbstractCompilationTest(true) {
2829
@Nested
2930
@DisplayName("Simple field resolver")
3031
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -213,8 +214,63 @@ internal class FieldResolverGeneratorTest : AbstractMockCompilationTest() {
213214
}
214215
}
215216

217+
@Nested
218+
@DisplayName("Resolver interface clashes")
219+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
220+
inner class ResolverDuplicates {
221+
private val variationOne: GraphQLObjectType =
222+
GraphQLObjectType.newObject()
223+
.name("User")
224+
.field(
225+
GraphQLFieldDefinition.newFieldDefinition()
226+
.name("emailAddress")
227+
.type(Scalars.GraphQLString)
228+
)
229+
.build()
230+
231+
private val variationTwo: GraphQLObjectType =
232+
GraphQLObjectType.newObject()
233+
.name("UserEmail")
234+
.field(
235+
GraphQLFieldDefinition.newFieldDefinition()
236+
.name("address")
237+
.type(Scalars.GraphQLString)
238+
)
239+
.build()
240+
241+
@Test
242+
fun `similar generated resolvers should not clash because of the same package and class name`() {
243+
val firstGenerator = buildGenerator(variationOne, variationOne.getFieldDefinition("emailAddress"))
244+
val secondGenerator = buildGenerator(variationTwo, variationTwo.getFieldDefinition("address"))
245+
246+
// It's sufficient to just compile as the compilation would fail due to duplicate classes.
247+
compile(firstGenerator, secondGenerator)
248+
}
249+
250+
/**
251+
* Will build a [FieldResolverGenerator] with the given [container] and [field]. This will get all other
252+
* dependencies from the [TestObject].
253+
*/
254+
private fun buildGenerator(
255+
container: GraphQLFieldsContainer,
256+
field: GraphQLFieldDefinition
257+
): FieldResolverGenerator =
258+
FieldResolverGenerator(
259+
container, field,
260+
TestObject.implementerMapper,
261+
TestObject.argumentCodeBlockGenerator,
262+
TestObject.options.copy(globalContext = "kotlin.String"),
263+
TestObject.kotlinTypeMapper,
264+
TestObject.generatedMapper
265+
)
266+
}
267+
216268
/**
217-
* Will return the "Env" class from the current [generatedClass].
269+
* Will return a reference to the "Env" class of the given [KClass] as [KClass]. The This will additionally
270+
* assert against null on the search result for the class.
271+
*
272+
* @param generated The class of which the inner class shall be resolved.
273+
* @return The [KClass] reference to the "Env" class.
218274
*/
219275
private fun getEnvClass(generated: KClass<*>): KClass<*> {
220276
val first = generated.nestedClasses.firstOrNull { it.simpleName == "Env" }
@@ -223,7 +279,11 @@ internal class FieldResolverGeneratorTest : AbstractMockCompilationTest() {
223279
}
224280

225281
/**
226-
* Will return the "resolve" function from the current [generatedClass].
282+
* Will return a reference to the "resolve" function for the given [KClass] as [KFunction]. This will additional
283+
* assert against null on the search result for the function.
284+
*
285+
* @param generated The class of which the function shall be resolved.
286+
* @return The [KFunction] reference to the "Env" class.
227287
*/
228288
private fun getResolveFunction(generated: KClass<*>): KFunction<*> {
229289
val first = generated.memberFunctions.firstOrNull { it.name == "resolve" }
@@ -232,20 +292,23 @@ internal class FieldResolverGeneratorTest : AbstractMockCompilationTest() {
232292
}
233293
}
234294

235-
val testSimpleFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
236-
.name("simple")
237-
.type(Scalars.GraphQLString)
238-
.build()
239-
240-
val testArgumentFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
241-
.name("arguments")
242-
.argument(GraphQLArgument.newArgument().name("bool").type(GraphQLNonNull(Scalars.GraphQLBoolean)).build())
243-
.argument(GraphQLArgument.newArgument().name("int").type(GraphQLNonNull(Scalars.GraphQLInt)).build())
244-
.type(GraphQLNonNull(Scalars.GraphQLString))
245-
.build()
246-
247-
val testObjectType = GraphQLObjectType.newObject()
248-
.name("TestObjectType")
249-
.field(testSimpleFieldDefinition)
250-
.field(testArgumentFieldDefinition)
251-
.build()
295+
val testSimpleFieldDefinition: GraphQLFieldDefinition =
296+
GraphQLFieldDefinition.newFieldDefinition()
297+
.name("simple")
298+
.type(Scalars.GraphQLString)
299+
.build()
300+
301+
val testArgumentFieldDefinition: GraphQLFieldDefinition =
302+
GraphQLFieldDefinition.newFieldDefinition()
303+
.name("arguments")
304+
.argument(GraphQLArgument.newArgument().name("bool").type(GraphQLNonNull(Scalars.GraphQLBoolean)).build())
305+
.argument(GraphQLArgument.newArgument().name("int").type(GraphQLNonNull(Scalars.GraphQLInt)).build())
306+
.type(GraphQLNonNull(Scalars.GraphQLString))
307+
.build()
308+
309+
val testObjectType: GraphQLObjectType =
310+
GraphQLObjectType.newObject()
311+
.name("TestObjectType")
312+
.field(testSimpleFieldDefinition)
313+
.field(testArgumentFieldDefinition)
314+
.build()

0 commit comments

Comments
 (0)