Skip to content

Commit d3a15a8

Browse files
committed
Fix constructor-less types in collections when generating randomClassInstance
Fix #204
1 parent d03195d commit d3a15a8

File tree

4 files changed

+65
-4
lines changed

4 files changed

+65
-4
lines changed

CHANGELOG.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[discrete]
55
=== Added
66

7+
* https://github.com/serpro69/kotlin-faker/issues/208[#208] [core] Allow `StringProvider#regexify` to take Regex as input
78
* https://github.com/serpro69/kotlin-faker/pull/202[#202] [core] Allow `randomClassInstance` to directly use predefined generators
89

910
[discrete]
@@ -14,7 +15,7 @@
1415
[discrete]
1516
=== Fixed
1617

17-
* ...
18+
* https://github.com/serpro69/kotlin-faker/issues/204[#204] [core] Fix RandomClassProvider handling "constructor-less" types in collections
1819

1920
[discrete]
2021
=== Other

core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt

+9
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ class RandomClassProvider {
108108
@JvmSynthetic
109109
@PublishedApi
110110
internal fun <T : Any> KClass<T>.randomClassInstance(config: RandomProviderConfig): T {
111+
// https://github.com/serpro69/kotlin-faker/issues/212
112+
if (this.isInner) throw UnsupportedOperationException("Inner classes are not yet supported")
113+
111114
val defaultInstance: T? by lazy {
112115
if (config.constructorParamSize == -1 && config.constructorFilterStrategy == NO_ARGS) {
113116
randomPrimitiveOrNull() as T? ?: try {
@@ -127,6 +130,12 @@ class RandomClassProvider {
127130
) as T?
128131
}
129132

133+
// Handle cases where "constructor-less" type is not a direct parameter of the generated class,
134+
// but is a collection type, for example
135+
// https://github.com/serpro69/kotlin-faker/issues/204
136+
if (this.java.isEnum) return randomEnumOrNull() as T
137+
if (this.java.isPrimitive) return randomPrimitiveOrNull() as T
138+
130139
return objectInstance ?: defaultInstance ?: run {
131140
val constructors = constructors.filter { it.visibility == KVisibility.PUBLIC }
132141

core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt

+53-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import io.kotest.assertions.throwables.shouldNotThrow
66
import io.kotest.assertions.throwables.shouldThrow
77
import io.kotest.core.spec.style.DescribeSpec
88
import io.kotest.matchers.collections.shouldHaveSize
9+
import io.kotest.matchers.ints.shouldBeInRange
910
import io.kotest.matchers.maps.shouldHaveSize
1011
import io.kotest.matchers.shouldBe
1112
import io.kotest.matchers.shouldNotBe
1213
import io.kotest.matchers.string.shouldHaveLength
1314
import io.kotest.matchers.types.instanceOf
1415
import io.kotest.matchers.types.shouldNotBeSameInstanceAs
16+
import org.junit.jupiter.api.assertThrows
1517
import java.util.*
1618
import kotlin.reflect.full.declaredMemberProperties
1719

@@ -394,14 +396,28 @@ class RandomClassProviderTest : DescribeSpec({
394396
class TestClass(
395397
val list: List<Foo>,
396398
val set: Set<Bar>,
397-
val map: Map<String, Baz>
399+
val map: Map<String, Baz>,
400+
// "primitives" (see https://github.com/serpro69/kotlin-faker/issues/204 )
401+
val charList: List<Char>,
402+
val intSet: Set<Int>,
403+
val boolMap: Map<Boolean, Byte>,
404+
// enums (see https://github.com/serpro69/kotlin-faker/issues/204 )
405+
val enumList: List<TestEnum>,
406+
val enumSet: Set<TestEnum>,
407+
val enumMap: Map<TestEnum, TestEnum>,
398408
)
399409

400410
it("should generate Collections with default size 1") {
401411
val testClass = randomProvider.randomClassInstance<TestClass>()
402412
testClass.list shouldHaveSize 1
403413
testClass.set shouldHaveSize 1
404414
testClass.map shouldHaveSize 1
415+
testClass.charList shouldHaveSize 1
416+
testClass.intSet shouldHaveSize 1
417+
testClass.boolMap shouldHaveSize 1
418+
testClass.enumList shouldHaveSize 1
419+
testClass.enumSet shouldHaveSize 1
420+
testClass.enumMap shouldHaveSize 1
405421
}
406422

407423
it("should generate Collections with pre-configured size") {
@@ -411,14 +427,31 @@ class RandomClassProviderTest : DescribeSpec({
411427
testClass.list shouldHaveSize 10
412428
testClass.set shouldHaveSize 10
413429
testClass.map shouldHaveSize 10
430+
testClass.charList shouldHaveSize 10
431+
testClass.intSet shouldHaveSize 10
432+
// boolean-key based map can only have 1 or 2 entries, depending on the randomness of the generated key
433+
testClass.boolMap.size shouldBeInRange 1..2
434+
testClass.enumList shouldHaveSize 10
435+
// we only have 3 enum classes, so a set can't have more values total than that
436+
testClass.enumSet.size shouldBeInRange 1..3
437+
// enum-key based map can only have up to 3 entries in this case, depending on the randomness of the generated key
438+
testClass.enumMap.size shouldBeInRange 1..3
414439
}
415440
it("should generate Collections with pre-configured type generation") {
416441
val testClass = randomProvider.randomClassInstance<TestClass> {
417442
typeGenerator<List<Foo>> { listOf() }
443+
typeGenerator<List<Char>> { listOf() }
444+
typeGenerator<List<TestEnum>> { listOf() }
418445
}
419446
testClass.list shouldHaveSize 0
420447
testClass.set shouldHaveSize 1
421448
testClass.map shouldHaveSize 1
449+
testClass.charList shouldHaveSize 0
450+
testClass.intSet shouldHaveSize 1
451+
testClass.boolMap shouldHaveSize 1
452+
testClass.enumList shouldHaveSize 0
453+
testClass.enumSet shouldHaveSize 1
454+
testClass.enumMap shouldHaveSize 1
422455
}
423456
}
424457

@@ -464,6 +497,16 @@ class RandomClassProviderTest : DescribeSpec({
464497
}
465498
}
466499

500+
describe("an inner class") {
501+
it("should throw exception when generated directly") {
502+
assertThrows<UnsupportedOperationException> { randomProvider.randomClassInstance<Go.BuildNum>() }
503+
}
504+
it("should throw exception when included as constructor parameter") {
505+
class Test(val goBuild: Go.BuildNum)
506+
assertThrows<UnsupportedOperationException> { randomProvider.randomClassInstance<Test>() }
507+
}
508+
}
509+
467510
describe("RandomClassProvider configuration") {
468511
class Foo(val int: Int)
469512
class Bar(val foo: Foo)
@@ -724,12 +767,19 @@ enum class TestEnum {
724767

725768
@Suppress("CanSealedSubClassBeObject", "unused")
726769
sealed class TestSealedCls {
727-
object Kotlin : TestSealedCls()
770+
data object Kotlin : TestSealedCls()
728771
class Java : TestSealedCls()
729772
}
730773

731774
@Suppress("unused")
732-
class Go(val name: String) : TestSealedCls()
775+
class Go(val name: String) : TestSealedCls() {
776+
777+
class Version(val ver: String)
778+
779+
inner class BuildNum(v: Version, i: Int) {
780+
val innerVersion = "${v.ver}+$i"
781+
}
782+
}
733783

734784
object TestObject
735785

docs/src/orchid/resources/wiki/extras.md

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ There are some rules to keep in mind:
2929

3030
- By default, the constructor with the least number of arguments is used (This can be configured - read on.)
3131
- `kolin.Array` type in the constructor is not supported at the moment
32+
- Inner classes (either direct generation or as class parameter type) are not supported at the moment
3233

3334
Random instance generation is available through `Faker().randomProvider`:
3435

0 commit comments

Comments
 (0)