Skip to content

Commit

Permalink
Add possibility to configure RandomClassProvider on higher levels
Browse files Browse the repository at this point in the history
  • Loading branch information
serpro69 committed Nov 5, 2022
1 parent 8592041 commit cafab82
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.github.serpro69.kfaker.docs

import io.github.serpro69.kfaker.Faker
import io.github.serpro69.kfaker.provider.ConstructorFilterStrategy
import io.github.serpro69.kfaker.provider.FallbackStrategy
import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy
import io.github.serpro69.kfaker.provider.misc.FallbackStrategy
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/kotlin/io/github/serpro69/kfaker/Faker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package io.github.serpro69.kfaker

import io.github.serpro69.kfaker.provider.*
import io.github.serpro69.kfaker.provider.misc.CryptographyProvider
import io.github.serpro69.kfaker.provider.misc.RandomClassProvider
import io.github.serpro69.kfaker.provider.misc.StringProvider
import io.github.serpro69.kfaker.provider.unique.GlobalUniqueDataDataProvider

Expand All @@ -14,7 +15,8 @@ import io.github.serpro69.kfaker.provider.unique.GlobalUniqueDataDataProvider
*
* @property random provides public access to the functions of [RandomService].
* @property randomProvider provides additional functionality that is not covered by other data providers
* such as [address], [name], [internet], and so on. See [RandomProvider] for more details.
* such as [address], [name], [internet], and so on. See [RandomClassProvider] for more details.
* @property string provides functionality to generate strings from expressions/templates
* @property unique global provider for generation of unique values.
*/
class Faker @JvmOverloads constructor(internal val config: FakerConfig = fakerConfig { }) {
Expand All @@ -24,13 +26,15 @@ class Faker @JvmOverloads constructor(internal val config: FakerConfig = fakerCo

val unique by lazy { GlobalUniqueDataDataProvider() }

// misc providers
val crypto: CryptographyProvider by lazy { CryptographyProvider(fakerService) }
val randomProvider: RandomProvider by lazy { RandomProvider(config) }
val randomProvider: RandomClassProvider by lazy { RandomClassProvider(config) }
val string: StringProvider by lazy { StringProvider(fakerService) }

val separator: Separator by lazy { Separator(fakerService) }
val currencySymbol: CurrencySymbol by lazy { CurrencySymbol(fakerService) }

// dictionary-based providers
val address: Address by lazy { Address(fakerService) }
val adjective: Adjective by lazy { Adjective(fakerService) }
val ancient: Ancient by lazy { Ancient(fakerService) }
Expand Down
21 changes: 19 additions & 2 deletions core/src/main/kotlin/io/github/serpro69/kfaker/FakerConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package io.github.serpro69.kfaker

import io.github.serpro69.kfaker.provider.misc.RandomProviderConfig
import java.util.*

/**
Expand All @@ -12,6 +13,17 @@ class FakerConfig private constructor(
val random: Random,
val uniqueGeneratorRetryLimit: Int
) {
internal var randomProviderConfig: RandomProviderConfig? = null
private set

private constructor(
locale: String,
random: Random,
uniqueGeneratorRetryLimit: Int,
randomProviderConfig: RandomProviderConfig
) : this(locale, random, uniqueGeneratorRetryLimit) {
this.randomProviderConfig = randomProviderConfig
}

companion object {
@JvmStatic
Expand Down Expand Up @@ -40,6 +52,7 @@ class FakerConfig private constructor(
var random = Random()
var randomSeed: Long? = null
var uniqueGeneratorRetryLimit = 100
private var randomProviderConfig: RandomProviderConfig = RandomProviderConfig()

fun withLocale(locale: String): Builder {
this.locale = locale
Expand All @@ -62,8 +75,12 @@ class FakerConfig private constructor(
}

fun build() = randomSeed?.let {
FakerConfig(locale, Random(it), uniqueGeneratorRetryLimit)
} ?: FakerConfig(locale, random, uniqueGeneratorRetryLimit)
FakerConfig(locale, Random(it), uniqueGeneratorRetryLimit, randomProviderConfig)
} ?: FakerConfig(locale, random, uniqueGeneratorRetryLimit, randomProviderConfig)

fun randomProvider(configurator: RandomProviderConfig.() -> Unit) {
randomProviderConfig.apply(configurator)
}
}
}

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

import io.github.serpro69.kfaker.FakerConfig
import io.github.serpro69.kfaker.RandomService
Expand All @@ -15,21 +15,42 @@ import kotlin.reflect.KType
import kotlin.reflect.KVisibility

/**
* Provider for functionality not covered by the standard dictionary files.
* Provider functionality for generating random class instances.
*
* Inspired by [Creating a random instance of any class in Kotlin blog post](https://blog.kotlin-academy.com/creating-a-random-instance-of-any-class-in-kotlin-b6168655b64a).
*/
@Suppress("unused")
class RandomProvider internal constructor(fakerConfig: FakerConfig) {
private val randomService = RandomService(fakerConfig)
class RandomClassProvider {
private val fakerConfig: FakerConfig
private val randomService: RandomService

@PublishedApi
@JvmSynthetic
internal val config: RandomProviderConfig

internal constructor(fakerConfig: FakerConfig) {
this.fakerConfig = fakerConfig
randomService = RandomService(fakerConfig)
config = fakerConfig.randomProviderConfig ?: RandomProviderConfig()
}

private constructor(fakerConfig: FakerConfig, config: RandomProviderConfig) {
this.fakerConfig = fakerConfig
this.config = config
randomService = RandomService(fakerConfig)
}

fun configure(configurator: RandomProviderConfig.() -> Unit) {
config.apply(configurator)
}

/**
* Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
* otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
*
* @throws NoSuchElementException if [T] has no public constructor.
*/
inline fun <reified T : Any> randomClassInstance() = T::class.randomClassInstance(RandomProviderConfig())
inline fun <reified T : Any> randomClassInstance() = T::class.randomClassInstance(config)

/**
* Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
Expand All @@ -40,8 +61,7 @@ class RandomProvider internal constructor(fakerConfig: FakerConfig) {
* @throws NoSuchElementException if [T] has no public constructor.
*/
inline fun <reified T : Any> randomClassInstance(configurator: RandomProviderConfig.() -> Unit): T {
val config = RandomProviderConfig().apply(configurator)
return T::class.randomClassInstance(config)
return T::class.randomClassInstance(RandomProviderConfig().apply(configurator))
}

@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -156,9 +176,8 @@ class RandomProvider internal constructor(fakerConfig: FakerConfig) {
}
}


/**
* Configuration for [RandomProvider.randomClassInstance].
* Configuration for [RandomClassProvider.randomClassInstance].
*
* @property collectionsSize the size of the generated [Collection] type arguments.
* Defaults to `1`.
Expand Down Expand Up @@ -210,6 +229,16 @@ class RandomProviderConfig @PublishedApi internal constructor() {
inline fun <reified K : Any?> nullableTypeGenerator(noinline generator: () -> K?) {
nullableGenerators[K::class] = generator
}

fun reset() {
collectionsSize = 1
constructorParamSize = -1
constructorFilterStrategy = ConstructorFilterStrategy.NO_ARGS
fallbackStrategy = FallbackStrategy.USE_MIN_NUM_OF_ARGS
namedParameterGenerators.clear()
predefinedGenerators.clear()
nullableGenerators.clear()
}
}

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

import io.github.serpro69.kfaker.FakerConfig
import io.github.serpro69.kfaker.fakerConfig
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldNotThrow
Expand All @@ -14,9 +15,11 @@ import io.kotest.matchers.types.instanceOf
import java.util.*

@Suppress("unused")
class RandomProviderTest : DescribeSpec({
class RandomClassProviderTest : DescribeSpec({
assertSoftly = true

val config = fakerConfig { random = Random() }
val randomProvider = RandomProvider(config)
val randomProvider = RandomClassProvider(config)

describe("a TestClass with an empty constructor") {
class TestClass
Expand Down Expand Up @@ -419,6 +422,167 @@ class RandomProviderTest : DescribeSpec({
randomProvider.randomClassInstance<String>() shouldHaveLength 24
}
}

describe("RandomClassProvider configuration") {
class Foo(val int: Int)
class Bar(val foo: Foo)
class Baz(val foo: Foo, val bar: Bar, val test: Int, val nullable: String?)

val cfg: () -> FakerConfig = {
fakerConfig {
randomProvider {
typeGenerator { Foo(0) }
nullableTypeGenerator { "nn" }
namedParameterGenerator("test") { 1 }
}
}
}

it("should configure all random instances") {
with(RandomClassProvider(config)) {
configure {
typeGenerator { Foo(42) }
nullableTypeGenerator { "nn" }
namedParameterGenerator("test") { 32167 }
}
randomClassInstance<Bar>().foo.int shouldBe 42
randomClassInstance<Baz>().foo.int shouldBe 42
randomClassInstance<Baz>().bar.foo.int shouldBe 42
randomClassInstance<Baz>().test shouldBe 32167
randomClassInstance<Baz>().nullable shouldBe "nn"
}
}

context("precedence") {
it("should configure random providers from fakerConfig") {
with(RandomClassProvider(cfg())) {
randomClassInstance<Bar>().foo.int shouldBe 0
randomClassInstance<Baz>().foo.int shouldBe 0
randomClassInstance<Baz>().bar.foo.int shouldBe 0
randomClassInstance<Baz>().test shouldBe 1
randomClassInstance<Baz>().nullable shouldBe "nn"
}
}
it("should have defaults if not configured on fakerConfig level") {
with(RandomClassProvider(fakerConfig { })) {
randomClassInstance<Bar>().foo.int shouldNotBe 0
randomClassInstance<Baz>().foo.int shouldNotBe 0
randomClassInstance<Baz>().bar.foo.int shouldNotBe 0
randomClassInstance<Baz>().test shouldNotBe 1
randomClassInstance<Baz>().nullable shouldNotBe "nn"
}
}
it("should override configuration from RandomProvider level") {
with(RandomClassProvider(cfg())) {
configure {
typeGenerator { Foo(42) }
nullableTypeGenerator { "just a string" }
namedParameterGenerator("test") { 32167 }
}
randomClassInstance<Bar>().foo.int shouldBe 42
randomClassInstance<Baz>().foo.int shouldBe 42
randomClassInstance<Baz>().bar.foo.int shouldBe 42
randomClassInstance<Baz>().test shouldBe 32167
randomClassInstance<Baz>().nullable shouldBe "just a string"
}
}
it("should re-configure random providers") {
with(RandomClassProvider(cfg())) {
configure {
typeGenerator { Foo(42) }
nullableTypeGenerator { "just a string" }
namedParameterGenerator("test") { 32167 }
}
randomClassInstance<Bar>().foo.int shouldBe 42
randomClassInstance<Baz>().foo.int shouldBe 42
randomClassInstance<Baz>().bar.foo.int shouldBe 42
randomClassInstance<Baz>().test shouldBe 32167
randomClassInstance<Baz>().nullable shouldBe "just a string"

configure {
typeGenerator { Foo(12177) }
nullableTypeGenerator<String> { null }
}
randomClassInstance<Bar>().foo.int shouldBe 12177
randomClassInstance<Baz>().foo.int shouldBe 12177
randomClassInstance<Baz>().bar.foo.int shouldBe 12177
randomClassInstance<Baz>().test shouldBe 32167
randomClassInstance<Baz>().nullable shouldBe null
}
}
it("should use own configuration on function level") {
with(RandomClassProvider(cfg())) {
configure {
typeGenerator { Foo(12177) }
namedParameterGenerator("test") { 32167 }
nullableTypeGenerator<String> { null }
}
randomClassInstance<Baz> { typeGenerator { Foo(36) } }.also {
it.foo.int shouldBe 36
it.bar.foo.int shouldBe 36
it.test shouldNotBe 1
it.test shouldNotBe 32167
it.nullable shouldNotBe "nn"
it.nullable shouldNotBe null
}
}
}
}

context("resetting to defaults") {
it("should reset configuration to defaults on RandomProvider level") {
with(RandomClassProvider(cfg())) {
configure { reset() }
randomClassInstance<Bar>().foo.int shouldNotBe 0
randomClassInstance<Baz>().foo.int shouldNotBe 0
randomClassInstance<Baz>().bar.foo.int shouldNotBe 0
randomClassInstance<Baz>().test shouldNotBe 1
randomClassInstance<Baz>().nullable shouldNotBe "nn"
}
}
it("should re-configure random providers") {
with(RandomClassProvider(cfg())) {
configure {
reset()
typeGenerator { Foo(12177) }
nullableTypeGenerator<String> { null }
}
randomClassInstance<Bar>().foo.int shouldBe 12177
randomClassInstance<Baz>().foo.int shouldBe 12177
randomClassInstance<Baz>().bar.foo.int shouldBe 12177
randomClassInstance<Baz>().test shouldNotBe 1
randomClassInstance<Baz>().nullable shouldBe null
}
}
it("should reset configuration to defaults on fakerConfig level") {
val c = cfg()
with(RandomClassProvider(c)) {
configure {
typeGenerator { Foo(12177) }
nullableTypeGenerator<String> { null }
}
c.randomProviderConfig?.reset()
randomClassInstance<Bar>().foo.int shouldNotBe 12177
randomClassInstance<Baz>().foo.int shouldNotBe 12177
randomClassInstance<Baz>().bar.foo.int shouldNotBe 12177
randomClassInstance<Baz>().test shouldNotBe 1
randomClassInstance<Baz>().nullable shouldNotBe null
}
}
}

context("new instance of RandomClassProvider") {
it("should have same configuration as defined on fakerConfig level") {

}
}

context("copy of RandomClassProvider") {
it("should have same configuration as defined on RandomClassProvider level") {

}
}
}
})

@Suppress("unused")
Expand Down

0 comments on commit cafab82

Please sign in to comment.