Random class instance¶
It is possible to create a random instance of (almost) any class.
There are some rules to keep in mind:
- A class must have a public or internal constructor
- By default, a 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
:
Random Class Instance Configuration¶
Random Class Instance configuration can be applied on several levels. Consider the following classes:
Configuration via FakerConfig
¶
This takes the least precedence and applies to all instances (see Making a copy/new instance of RandomClassProvider) of RandomClassProvider
if set.
val cfg = fakerConfig {
randomClassInstance {
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
}
val f = Faker(cfg)
val baz: Baz = f.randomClass.randomClassInstance<Baz>()
assertEquals(baz.bar, Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")))
val anotherBaz = f.randomClass.new().randomClassInstance<Baz>()
assertEquals(anotherBaz.bar, Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")))
Configuration via Faker#randomProvider
¶
This takes higher precedence and will also merge any configuration that was set on the previous level.
val cfg = fakerConfig {
randomClassInstance {
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
}
val f = Faker(cfg).also {
it.randomClass.configure {
typeGenerator<UUID> { UUID.fromString("00000000-0000-0000-0000-000000000000") }
}
}
val bar: Bar = f.randomClass.randomClassInstance()
val baz: Baz = f.randomClass.randomClassInstance()
assertEquals(bar.uuid, UUID.fromString("00000000-0000-0000-0000-000000000000"))
assertEquals(baz.bar, Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")))
Configuration via randomClassInstance
function¶
This configuration takes the most precedence and does not take into account configurations applied on other levels.
faker.randomClass.configure {
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val baz: Baz = faker.randomClass.randomClassInstance {
typeGenerator<Bar> { Bar(1, UUID.fromString("00000000-0000-0000-0000-000000000000")) }
}
assertEquals(baz.bar, Bar(1, UUID.fromString("00000000-0000-0000-0000-000000000000")))
Pre-Configuring type generation¶
Predefined types for constructor parameters¶
Some, or all, of the constructor params can be instantiated with values following some pre-configured logic using typeGenerator
or namedParameterGenerator
functions. Consider the following example:
class Baz(val id: Int, val uuid: UUID, val relatedUuid: UUID, val user: String)
val baz: Baz = faker.randomClass.randomClassInstance {
typeGenerator<UUID> {
UUID.fromString("00000000-0000-0000-0000-000000000000")
}
typeGenerator<Int> { 0 }
typeGenerator<String> { parameterInfo ->
"${parameterInfo.name}_${randomString()}"
}
namedParameterGenerator("relatedUuid") {
UUID.fromString("11111111-1111-1111-1111-111111111111")
}
}
So for each instance of Baz
the following will be true:
This example itself does not make that much sense, since we're using "static" values, but we could also do something like:
...or even so:
Pre-defined instance for classes with no public or internal constructors¶
By default, randomClassInstance
can't generate classes with no public constructors, but this can be worked around by using typeGenerator
:
You could also use the same approach to create interfaces, for example:
interface TestInterface {
val id: Int
val name: String
}
val testInterface = randomProvider.randomClassInstance<TestInterface> {
typeGenerator<TestInterface> {
object : TestInterface {
override val id: Int = 42
override val name: String = "Deep Thought"
}
}
}
testInterface.id shouldBe 42
testInterface.name shouldBe "Deep Thought"
A random class instance will be generated using the following precedence rules:
- object instance
- "default instance" of a class
- uses a public/internal constructor with the least number of arguments, unless otherwise configured (see Random Class Instance Configuration)
- "predefined instance" of a class if no public or internal constructors are found
- failing all of the above,
NoSuchElementException
will be thrown
Predefined collection element types¶
It may be desirable to define how elements of a Collection
(currently supports List
s and Set
s) constructor parameter type are generated, for this collectionElementTypeGenerator
function can be used:
fun randomListString() = "list"
fun randomString() = "string"
class Baz(val list: List<String>, val set: Set<String>)
val baz: Baz = faker.randomClass.randomClassInstance {
collectionElementTypeGenerator<String> {
// customize generators for different collection types
if ((it.type.classifier as KClass<*>) == List::class) {
// generate random string elements for parameters of List<String> type
randomListString()
} else {
// generate random string elements for parameters of Set type
randomString()
}
}
}
So for each instance of Baz
the following will be true:
This example kind of makes little sense, since we're using "static" values, so it's just for example purposes.
Info
Note how we can use it.type.classifier
to figure out the parameter classifier and further customize generation for different collection types.
Explore other properties of it
, which exposes more details about the parameter that is being customized.
There are also two similar methods for map entries: mapEntryKeyTypeGenerator
and mapEntryValueTypeGenerator
, which can be used to configure how keys and values are generated in Map
constructor parameter types:
Nullable collection element types are also supported (but note that null
s as values are never returned):
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")
Deterministic constructor selection¶
By default, the constructor with the least number of args is used when creating a random instance of the class. This might not always be desirable and can be configured. Consider the following example:
class Foo
class Bar(val int: Int)
class Baz(val foo: Foo, val string: String)
class FooBarBaz {
var foo: Foo? = null
private set
var bar: Bar? = null
private set
var baz: Baz? = null
private set
constructor(foo: Foo) {
this.foo = foo
}
constructor(foo: Foo, bar: Bar) : this(foo) {
this.bar = bar
}
constructor(foo: Foo, bar: Bar, baz: Baz) : this(foo, bar) {
this.baz = baz
}
}
If there is a need to use the constructor with 3 arguments when creating an instance of FooBarBaz
, we can do it like so:
In the above example, FooBarBaz
will be instantiated with the first discovered constructor that has parameters.size == 3
; if there are multiple constructors that satisfy this condition - a random one will be used. Failing that (for example, if there is no such constructor), a constructor with the maximum number of arguments will be used to create an instance of the class.
Alternatively to constructorParamSize
, a constructorFilterStrategy
config property can be used as well:
The above has the following rules:
constructorParamSize
config property takes precedence overconstructorFilterStrategy
- both can be specified at the same time, though in most cases it probably makes more sense to use
fallbackStrategy
withconstructorParamSize
as it just makes things a bit more readable - configuration properties that are set in
randomClassInstance
block will be applied to all "children" classes. For example classesFoo
,Bar
, andBaz
will use the same random instance configuration settings when instances of those classes are created inFooBarBaz
class.
Default values selection¶
By default, all parameters of a selected constructor would be generated with randomized values:
class Foo(val i: Int)
class TestClass(
val iMin: Int = Int.MIN_VALUE,
val iMax: Int = Int.MAX_VALUE,
val s: String = "sometimes a string... is just a string",
val foo: Foo = Foo(369)
)
val testClass: TestClass = Faker().randomClass.randomClassInstance()
assertNotEquals(Int.MIN_VALUE, testClass.iMin)
assertNotEquals(Int.MAX_VALUE, testClass.iMax)
assertNotEquals("sometimes a string... is just a string", testClass.s)
assertNotEquals(369, testClass.foo.i)
This behavior can be changed with defaultValuesStrategy
configuration option.
One can choose between generating values only for non-optional constructor parameters:
...or randomly selecting between a default value and a randomly-generated one:
it("should randomly pick a default or a random value") {
val testClass: TestClass = Faker().randomClass.randomClassInstance {
defaultValuesStrategy = PICK_RANDOMLY
}
assert(
testClass.iMin == Int.MIN_VALUE
|| testClass.iMax == Int.MAX_VALUE
|| testClass.s == "sometimes a string... is just a string"
|| testClass.foo.i == 369
)
assert(!(
testClass.iMin == Int.MIN_VALUE
&& testClass.iMax == Int.MAX_VALUE
&& testClass.s == "sometimes a string... is just a string"
&& testClass.foo.i == 369
))
Configuring the size of generated Collections¶
Support for kotlin.collections.Collection
parameter types - List
, Set
and Map
has been added in version 1.9.0
and with that - a new configuration parameter to configure the size of the generated collection.
By default, all collections will be generated with only 1 element:
This can be configured using collectionsSize
parameter:
Info
Note that the collectionsSize
configuration parameter affects all 3 types of Collections.
Warning
It is also worth noting that typeGenerator<Foo> { ... }
configuration, which was covered above, will not affect Foo
typed elements in a generated collection.
Consider the following example. If typeGenerator<String> { "a string" }
would affect String
typed elements of Set
, the resulting generated set would be of size 1
:
At the same time, typeGenerator
configurator itself can be used with collections as well:
class Foo
class Bar(
val list: List<Foo>,
val set: Set<String>,
val map: Map<String, Int>
)
val bar = faker.randomClass.randomClassInstance<Bar> {
typeGenerator { emptyList<Foo>() }
typeGenerator { setOf("one", "two", "fortytwo") }
typeGenerator { mapOf("pwd" to 12177) }
}
assertEquals(bar.list, emptyList<Foo>())
assertEquals(bar.set, setOf("one", "two", "fortytwo"))
assertEquals(bar.map, mapOf("pwd" to 12177))
Making a new instance of Random Class Provider¶
RandomClassProvider
has two functions: new
and copy
, that allow you to create another instance of the class, for example, a one that has a different type generation configuration.
New Instance¶
To make a new instance of randomProvider
:
val cfg = fakerConfig {
randomClassInstance { // ❶
typeGenerator<Bar> { Bar(1, UUID.fromString("00000000-0000-0000-0000-000000000000")) }
}
}
val f = Faker(cfg)
f.randomClass.configure { // ❷
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val new = f.randomClass.new() // ❸
val baz: Baz = f.randomClass.randomClassInstance<Baz>()
val newBaz: Baz = new.randomClassInstance<Baz>()
assertEquals(Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")), baz.bar)
assertEquals(Bar(1, UUID.fromString("00000000-0000-0000-0000-000000000000")), newBaz.bar)
Info
Any configuration set via fakerConfig
( ❶ ), will be applied to the new
instance ( ❸ ) as well.
Any configuration set via faker.randomProvider
instance ( ❷ ) is NOT applied to the new
instance.
Instance Copy¶
To make a copy of an existing instance of randomProvider
:
val cfg = fakerConfig { // ❶
randomClassInstance {
typeGenerator<Bar> { Bar(1, UUID.fromString("00000000-0000-0000-0000-000000000000")) }
}
}
val f = Faker(cfg)
f.randomClass.configure { // ❷
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val copy = f.randomClass.copy() // ❸
val baz: Baz = f.randomClass.randomClassInstance<Baz>()
val bazCopy: Baz = copy.randomClassInstance<Baz>()
assertEquals(Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")), baz.bar)
assertEquals(Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")), bazCopy.bar)
copy.configure { // ❹
typeGenerator<Bar> { Bar(0, UUID.fromString("22222222-2222-2222-2222-222222222222")) }
}
val originalBaz: Baz = f.randomClass.randomClassInstance<Baz>()
val reconfiguredBazCopy = copy.randomClassInstance<Baz>()
assertEquals(Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")), originalBaz.bar)
assertEquals(Bar(0, UUID.fromString("22222222-2222-2222-2222-222222222222")), reconfiguredBazCopy.bar)
Info
Any configuration that was already applied to faker.randomProvider
( ❶ and ❷ ), will be applied to the copy
( ❸ ) as well.
The copy
, just as new
instance, can of course be reconfigured ( ❹ ) as needed, which does not affect the configuration of the faker.randomProvider
or configurations of other "copies".
Dealing with Generic Types¶
Generic parameter types are not fully supported at this moment due to type-erasure on the JVM (See also https://github.com/serpro69/kotlin-faker/issues/191)
Top-level functions¶
Sometimes you just want to generate random POJO instances and don't need the whole range of kotlin-faker functionality? For these cases you can import randomClassInstance
top-level function and use it directly w/o the need to instantiate an instance of Faker
It can also be configured via configurator
lambda parameter, or via FakerConfig
instance:
import io.github.serpro69.kfaker.fakerConfig
import io.github.serpro69.kfaker.randomClassInstance
import java.time.Instant
val randomClass = randomClassInstance<MyClass>() {
typeGenerator<Instant> { Instant.now() }
}
val cfg = fakerConfig {
randomClassInstance {
typeGenerator<Instant> { Instant.MIN }
}
}
val another = randomClassInstance<MyClass>(cfg)