Extras
kotlin-faker
provides additional functionality outside of data generation from static .yml dictionaries.
ToC
- Random instance of any class
- Random Everything
- Random Strings from Templates
Random instance of any class
It is possible to create a random instance of (almost) any class.
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
:
class Foo(val a: String)
class Bar(val foo: Foo)
val foo: Foo = faker.randomClass.randomClassInstance()
val bar: Bar = faker.randomClass.randomClassInstance()
back-to-toc
Random Class Instance Configuration
Random Class Instance configuration can be applied on several levels. Consider the following classes:
class Foo
data class Bar(val int: Int, val uuid: UUID)
data class Baz(val foo: Foo, val bar: Bar, val string: String)
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")))
back-to-toc
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:
baz.id == 0
baz.user == "user_X3a8s813dcb"
baz.uuid == UUID.fromString("00000000-0000-0000-0000-000000000000")
baz.relatedUuid == UUID.fromString("11111111-1111-1111-1111-111111111111")
This example itself does not make that much sense, since we're using "static" values, but we could also do something like:
val baz: Baz = faker.randomProvider.randomClassInstance {
typeGenerator<UUID> { UUID.randomUUID() }
}
...or even so:
class Person(val id: Int, val name: String)
val person: Person = faker.randomProvider.randomClassInstance {
typeGenerator<String> { faker.name.name() }
}
Pre-defined instance for classes with no public constructors
By default, randomClassInstance
can't generate classes with no public constructors, but this can be worked around by using typeGenerator
:
faker.randomProvider.randomClassInstance<Instant> {
typeGenerator<Instant> { Instant.now() }
}
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 the public constructor with the least number of arguments, unless otherwise configured (see Random Class Instance Configuration)
- "predefined instance" of a class if no public constructors are found
- failing all of the above,
NoSuchElementException
will be thrown
back-to-toc
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:
baz.list.all { it == "list" }
baz.set.all {it == "string" }
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:
class Baz(val map: Map<String, String>)
val baz: Baz = faker.randomClass.randomClassInstance {
mapEntryKeyTypeGenerator { randomKey() }
mapEntryValueTypeGenerator { randomValue() }
}
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:
val fooBarBaz: FooBarBaz = faker.randomClass.randomClassInstance {
constructorParamSize = 3
fallbackStrategy = FallbackStrategy.USE_MAX_NUM_OF_ARGS
}
assertNotEquals(fooBarBaz.foo, null)
assertNotEquals(fooBarBaz.bar, null)
assertNotEquals(fooBarBaz.baz, null)
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:
val fooBarBaz: FooBarBaz = faker.randomClass.randomClassInstance {
constructorFilterStrategy = ConstructorFilterStrategy.MAX_NUM_OF_ARGS
}
assertNotEquals(fooBarBaz.foo, null)
assertNotEquals(fooBarBaz.bar, null)
assertNotEquals(fooBarBaz.baz, null)
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.
back-to-toc
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:
class Foo(
val list: List<String>,
val set: Set<String>,
val map: Map<String, Int>
)
val foo = faker.randomClass.randomClassInstance<Foo>()
assertEquals(foo.list.size, 1)
assertEquals(foo.set.size, 1)
assertEquals(foo.map.size, 1)
This can be configured using collectionsSize
parameter:
class Foo(
val list: List<String>,
val set: Set<String>,
val map: Map<String, Int>
)
val foo = faker.randomClass.randomClassInstance<Foo> {
collectionsSize = 6
}
assertEquals(foo.list.size, 6)
assertEquals(foo.set.size, 6)
assertEquals(foo.map.size, 6)
info
Note that the collectionsSize
configuration parameter affects all 3 types of Collections.
warn
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
:
class TestClass(
val string: String,
val set: Set<String>
)
val testClass = faker.randomClass.randomClassInstance<TestClass> {
typeGenerator { "a string" }
collectionsSize = 10
}
assertEquals(testClass.string, "a string")
assertEquals(testClass.set.size, 10)
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))
back-to-toc
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".
back-to-toc
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)
back-to-toc
Random Everything
Faker provides its wrapper functions around java.util.Random
(with some additional functionality that is not covered by java.util.Random
) through Faker().random
property.
Wrappers around java.util.Random
faker.random.nextBoolean()
faker.random.nextChar()
faker.random.nextDouble()
faker.random.nextFloat()
faker.random.nextInt()
faker.random.nextInt(bound = 100)
faker.random.nextInt(min = 100, max = 999)
faker.random.nextInt(intRange = (0..99))
faker.random.nextLetter(upper = false)
Random Enum Instance
enum class Foo {
ONE,
TWO,
FORTY_TWO
}
faker.random.nextEnum<Foo>()
faker.random.nextEnum(enum = Foo::class.java)
faker.random.nextEnum(values = Foo.values())
faker.random.nextEnum(enum = Foo::class.java) { it != Foo.ONE }
faker.random.nextEnum<Foo>("ONE")
Random Strings
faker.random.randomString(
length = 42,
numericalChars = false
)
faker.random.randomString(
length = 24,
locale = Locale.forLanguageTag("nb-NO"),
indexChars = true,
auxiliaryChars = true,
punctuationChars = true,
numericalChars = true,
)
Random sub-lists and sub-sets
val list = List(100) { it }
faker.random.randomSublist(list, size = 10, shuffled = true)
faker.random.randomSublist(list, sizeRange = 6..42, shuffled = true)
val set = setOf(*List(100) { it }.toTypedArray())
faker.random.randomSubset(set, size = 10, shuffled = true)
faker.random.randomSubset(set, sizeRange = 66..99, shuffled = true)
Random element from a list/array
val list = listOf(1, 2, 3)
faker.random.randomValue(list)
Random UUID
faker.random.nextUUID()
Unique Random Values
Just like most data providers, Faker#random
supports generation of unique values. See Generator of Unique Values page for usage details.
Both "local" (provider level) and "global" (faker level) generation of unique values are supported for RandomProvider
:
val ints = List(21) {
faker.random.unique.nextInt(42)
}
assert(ints.distinct().size == 21)
// cleanup of unique values via enum key for nextInt function
faker.random.unique.clear(RandomProvider.Key.NEXT_INT)
faker.unique.configuration { enable(faker::random) }
val uniqueInts = List(21) {
faker.random.nextInt(42)
}
assert(uniqueInts.distinct().size == 21)
// cleanup global unique values for Random provider
faker.unique.clear(faker::random)
// disable global unique values for Random provider
faker.unique.configuration { disable(faker::random) }
val ints = List(21) {
faker.random.nextInt(42)
}
assert(ints.distinct().size < 21)
back-to-toc
Random Strings from Templates
Faker's StringProvider
allows for replacing certain user-defined parts of strings with randomly generated chars (letters and digits), as well as generating strings from regex expressions.
The following functions are available withing the Faker().string
:
faker.string.numerify("123###").all { it.isDigit() } shouldBe true
faker.string.letterify("foo???").all { it.isLetter() } shouldBe true
faker.string.letterify("???BAR", true).all { it.isUpperCase() } shouldBe true
faker.string.letterify("???bar", false).all { it.isLowerCase() } shouldBe true
faker.string.bothify("foo???bar###")
faker.string.regexify("""\d{42}""").all { it.isDigit() } shouldBe true
faker.string.regexify("""\d{42}""").length shouldBe 42
back-to-toc