Skip to content

Commit

Permalink
Extract common 'random collection element' logic
Browse files Browse the repository at this point in the history
Update tests and docs for nullable elements

Relates to #243
  • Loading branch information
serpro69 committed Jun 30, 2024
1 parent 890991b commit 82a23fc
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy
import io.github.serpro69.kfaker.provider.misc.FallbackStrategy
import io.github.serpro69.kfaker.provider.misc.RandomProvider
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.containOnly
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.collections.shouldContainAnyOf
import io.kotest.matchers.collections.shouldNotContain
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNot
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import java.util.*
Expand Down Expand Up @@ -87,6 +92,36 @@ class Extras : DescribeSpec({
assertEquals(baz.map.keys.all { it == "key" }, true)
assertEquals(baz.map.values.all { it == "value" }, true)
}

it("should allow nullable element types in collections") {
// START extras_random_instance_eighteen
data class Nullable(val ints: List<Int?>, val longs: Set<Long?>, val map: Map<Char?, String?>)
val nullable = faker.randomClass.randomClassInstance<Nullable> {
collectionsSize = 10
collectionElementTypeGenerator<Int?> { if (faker.random.nextBoolean()) null else 42 }
collectionElementTypeGenerator<Long?> { if (!faker.random.nextBoolean()) 0L else null }
mapEntryKeyTypeGenerator<Char> { faker.random.randomValue(listOf('a', 'b', 'c', 'd', 'e', 'f')) }
mapEntryValueTypeGenerator<String?> { if (faker.random.nextBoolean()) null else "foo" }
}
nullable.ints shouldContain 42
// we allow nullable values, but `null` as a value will never be returned
nullable.ints shouldNotContain null
// with above config, if nextBoolean returns false, we say "return null",
// but since nulls are never returned as value, all nulls will be returned as random instance,
// hence we won't have all 42's
nullable.ints shouldNot containOnly(42)

nullable.longs shouldContain 0L
nullable.longs shouldNotContain null
nullable.longs shouldNot containOnly(0L)

nullable.map.keys shouldNotContain null
nullable.map.keys shouldContainAnyOf listOf('a', 'b', 'c', 'd', 'e', 'f')
nullable.map.values shouldNotContain null
nullable.map.values shouldContain "foo"
nullable.map.values shouldNot containOnly("foo")
// END extras_random_instance_eighteen
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.serpro69.kfaker.provider.misc

import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate.Param
import io.github.serpro69.kfaker.FakerConfig
import io.github.serpro69.kfaker.RandomService
import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy.MAX_NUM_OF_ARGS
Expand Down Expand Up @@ -233,13 +234,13 @@ class RandomClassProvider {
config: RandomProviderConfig,
pInfo: ParameterInfo
): Any? {
val instance: (gen: Map<KClass<*>, (ParameterInfo) -> Any?>, el: KClass<*>) -> Any = { gen, el ->
gen[el]?.invoke(pInfo) ?: el.randomClassInstance(config)
}
return when (this) {
List::class, Set::class -> {
val elementType = kType.arguments[0].type?.classifier as KClass<*>
val r = List(config.collectionsSize) {
config.collectionElementTypeGenerators[elementType]?.invoke(pInfo)
?: elementType.randomClassInstance(config)
}
val r = List(config.collectionsSize) { instance(config.collectionElementTypeGenerators, elementType) }
when (this) {
List::class -> r
Set::class -> r.toSet()
Expand All @@ -249,14 +250,8 @@ class RandomClassProvider {
Map::class -> {
val keyElementType = kType.arguments[0].type?.classifier as KClass<*>
val valElementType = kType.arguments[1].type?.classifier as KClass<*>
val keys = List(config.collectionsSize) {
config.mapEntriesTypeGenerators.first[keyElementType]?.invoke(pInfo)
?: keyElementType.randomClassInstance(config)
}
keys.associateWith {
config.mapEntriesTypeGenerators.second[valElementType]?.invoke(pInfo)
?: valElementType.randomClassInstance(config)
}
val keys = List(config.collectionsSize) { instance(config.mapEntriesTypeGenerators.first, keyElementType) }
keys.associateWith { instance(config.mapEntriesTypeGenerators.second, valElementType) }
}
else -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ package io.github.serpro69.kfaker.provider.misc

import io.github.serpro69.kfaker.FakerConfig
import io.github.serpro69.kfaker.fakerConfig
import io.github.serpro69.kfaker.random
import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.containOnly
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.collections.shouldContainAnyOf
import io.kotest.matchers.collections.shouldContainOnly
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.collections.shouldNotContain
import io.kotest.matchers.collections.shouldNotContainOnly
import io.kotest.matchers.ints.shouldBeInRange
import io.kotest.matchers.maps.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNot
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldHaveLength
import io.kotest.matchers.types.instanceOf
Expand Down Expand Up @@ -503,6 +511,34 @@ class RandomClassProviderTest : DescribeSpec({
testClass.enumSet shouldBe emptySet()
testClass.enumMap shouldBe mapOf(TestEnum.JAVA to TestEnum.KOTLIN)
}

it("should generate null elements in collections") {
data class Nullable(val ints: List<Int?>, val longs: Set<Long?>, val map: Map<Char?, String?>)
val nullable = randomProvider.randomClassInstance<Nullable> {
collectionsSize = 10
collectionElementTypeGenerator<Int?> { if (random.nextBoolean()) null else 42 }
collectionElementTypeGenerator<Long?> { if (!random.nextBoolean()) 0L else null }
mapEntryKeyTypeGenerator<Char> { listOf('a', 'b', 'c', 'd', 'e', 'f').random() }
mapEntryValueTypeGenerator<String?> { if (random.nextBoolean()) null else "foo" }
}
nullable.ints shouldContain 42
// we allow nullable values, but `null` as a value will never be returned
nullable.ints shouldNotContain null
// with above config, if nextBoolean returns false, we say "return null",
// but since nulls are never returned as value, all nulls will be returned as random instance,
// hence we won't have all 42's
nullable.ints shouldNot containOnly(42)

nullable.longs shouldContain 0L
nullable.longs shouldNotContain null
nullable.longs shouldNot containOnly(0L)

nullable.map.keys shouldNotContain null
nullable.map.keys shouldContainAnyOf listOf('a', 'b', 'c', 'd', 'e', 'f')
nullable.map.values shouldNotContain null
nullable.map.values shouldContain "foo"
nullable.map.values shouldNot containOnly("foo")
}
}

describe("a TestClass with with abstract type constructor parameter") {
Expand Down Expand Up @@ -801,6 +837,7 @@ class RandomClassProviderTest : DescribeSpec({
copy.config shouldNotBeSameInstanceAs c.randomProviderConfig
}
}

it("should have same configuration as defined on fakerConfig level if not defined on RandomClassProvider level") {
val c = cfg()
with(RandomClassProvider(c)) {
Expand Down
14 changes: 14 additions & 0 deletions docs/src/orchid/resources/wiki/extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,20 @@ There are also two similar methods for map entries: `mapEntryKeyTypeGenerator` a

{% endtabs %}

Nullable collection element types are also supported (but note that `null`s as values are never returned):

{% tabs %}

{% kotlin "Kotlin" %}
{% filter compileAs('md') %}
```kotlin
{% snippet 'extras_random_instance_eighteen' %}
```
{% endfilter %}
{% endkotlin %}

{% endtabs %}

<br>

### Deterministic constructor selection
Expand Down

0 comments on commit 82a23fc

Please sign in to comment.