Skip to content

Commit

Permalink
Fix constructor-less types in collections when generating randomClass…
Browse files Browse the repository at this point in the history
…Instance

Fix #204
  • Loading branch information
serpro69 committed Feb 10, 2024
1 parent d03195d commit d3a15a8
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[discrete]
=== Added

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

[discrete]
Expand All @@ -14,7 +15,7 @@
[discrete]
=== Fixed

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

[discrete]
=== Other
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class RandomClassProvider {
@JvmSynthetic
@PublishedApi
internal fun <T : Any> KClass<T>.randomClassInstance(config: RandomProviderConfig): T {
// https://github.com/serpro69/kotlin-faker/issues/212
if (this.isInner) throw UnsupportedOperationException("Inner classes are not yet supported")

val defaultInstance: T? by lazy {
if (config.constructorParamSize == -1 && config.constructorFilterStrategy == NO_ARGS) {
randomPrimitiveOrNull() as T? ?: try {
Expand All @@ -127,6 +130,12 @@ class RandomClassProvider {
) as T?
}

// Handle cases where "constructor-less" type is not a direct parameter of the generated class,
// but is a collection type, for example
// https://github.com/serpro69/kotlin-faker/issues/204
if (this.java.isEnum) return randomEnumOrNull() as T
if (this.java.isPrimitive) return randomPrimitiveOrNull() as T

return objectInstance ?: defaultInstance ?: run {
val constructors = constructors.filter { it.visibility == KVisibility.PUBLIC }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.ints.shouldBeInRange
import io.kotest.matchers.maps.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldHaveLength
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldNotBeSameInstanceAs
import org.junit.jupiter.api.assertThrows
import java.util.*
import kotlin.reflect.full.declaredMemberProperties

Expand Down Expand Up @@ -394,14 +396,28 @@ class RandomClassProviderTest : DescribeSpec({
class TestClass(
val list: List<Foo>,
val set: Set<Bar>,
val map: Map<String, Baz>
val map: Map<String, Baz>,
// "primitives" (see https://github.com/serpro69/kotlin-faker/issues/204 )
val charList: List<Char>,
val intSet: Set<Int>,
val boolMap: Map<Boolean, Byte>,
// enums (see https://github.com/serpro69/kotlin-faker/issues/204 )
val enumList: List<TestEnum>,
val enumSet: Set<TestEnum>,
val enumMap: Map<TestEnum, TestEnum>,
)

it("should generate Collections with default size 1") {
val testClass = randomProvider.randomClassInstance<TestClass>()
testClass.list shouldHaveSize 1
testClass.set shouldHaveSize 1
testClass.map shouldHaveSize 1
testClass.charList shouldHaveSize 1
testClass.intSet shouldHaveSize 1
testClass.boolMap shouldHaveSize 1
testClass.enumList shouldHaveSize 1
testClass.enumSet shouldHaveSize 1
testClass.enumMap shouldHaveSize 1
}

it("should generate Collections with pre-configured size") {
Expand All @@ -411,14 +427,31 @@ class RandomClassProviderTest : DescribeSpec({
testClass.list shouldHaveSize 10
testClass.set shouldHaveSize 10
testClass.map shouldHaveSize 10
testClass.charList shouldHaveSize 10
testClass.intSet shouldHaveSize 10
// boolean-key based map can only have 1 or 2 entries, depending on the randomness of the generated key
testClass.boolMap.size shouldBeInRange 1..2
testClass.enumList shouldHaveSize 10
// we only have 3 enum classes, so a set can't have more values total than that
testClass.enumSet.size shouldBeInRange 1..3
// enum-key based map can only have up to 3 entries in this case, depending on the randomness of the generated key
testClass.enumMap.size shouldBeInRange 1..3
}
it("should generate Collections with pre-configured type generation") {
val testClass = randomProvider.randomClassInstance<TestClass> {
typeGenerator<List<Foo>> { listOf() }
typeGenerator<List<Char>> { listOf() }
typeGenerator<List<TestEnum>> { listOf() }
}
testClass.list shouldHaveSize 0
testClass.set shouldHaveSize 1
testClass.map shouldHaveSize 1
testClass.charList shouldHaveSize 0
testClass.intSet shouldHaveSize 1
testClass.boolMap shouldHaveSize 1
testClass.enumList shouldHaveSize 0
testClass.enumSet shouldHaveSize 1
testClass.enumMap shouldHaveSize 1
}
}

Expand Down Expand Up @@ -464,6 +497,16 @@ class RandomClassProviderTest : DescribeSpec({
}
}

describe("an inner class") {
it("should throw exception when generated directly") {
assertThrows<UnsupportedOperationException> { randomProvider.randomClassInstance<Go.BuildNum>() }
}
it("should throw exception when included as constructor parameter") {
class Test(val goBuild: Go.BuildNum)
assertThrows<UnsupportedOperationException> { randomProvider.randomClassInstance<Test>() }
}
}

describe("RandomClassProvider configuration") {
class Foo(val int: Int)
class Bar(val foo: Foo)
Expand Down Expand Up @@ -724,12 +767,19 @@ enum class TestEnum {

@Suppress("CanSealedSubClassBeObject", "unused")
sealed class TestSealedCls {
object Kotlin : TestSealedCls()
data object Kotlin : TestSealedCls()
class Java : TestSealedCls()
}

@Suppress("unused")
class Go(val name: String) : TestSealedCls()
class Go(val name: String) : TestSealedCls() {

class Version(val ver: String)

inner class BuildNum(v: Version, i: Int) {
val innerVersion = "${v.ver}+$i"
}
}

object TestObject

Expand Down
1 change: 1 addition & 0 deletions docs/src/orchid/resources/wiki/extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ There are some rules to keep in mind:

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

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

Expand Down

0 comments on commit d3a15a8

Please sign in to comment.