Extras
kotlin-faker
provides additional functionality outside of data generation from static .yml dictionaries.
ToC
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
Random instance generation is available through Faker().randomProvider
:
class Foo(val a: String)
class Bar(val foo: Foo)
val foo: Foo = faker.randomProvider.randomClassInstance()
val bar: Bar = faker.randomProvider.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.randomProvider.randomClassInstance<Baz>()
assertEquals(baz.bar, Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")))
val anotherBaz = f.randomProvider.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.randomProvider.configure {
typeGenerator<UUID> { UUID.fromString("00000000-0000-0000-0000-000000000000") }
}
}
val bar: Bar = f.randomProvider.randomClassInstance()
val baz: Baz = f.randomProvider.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.randomProvider.configure {
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val baz: Baz = faker.randomProvider.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 for constructor arguments
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.randomProvider.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() }
}
back-to-toc
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.randomProvider.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.randomProvider.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.randomProvider.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.randomProvider.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.randomProvider.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.randomProvider.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.randomProvider.configure { // ❷
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val new = f.randomProvider.new() // ❸
val baz: Baz = f.randomProvider.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.randomProvider.configure { // ❷
typeGenerator<Bar> { Bar(42, UUID.fromString("11111111-1111-1111-1111-111111111111")) }
}
val copy = f.randomProvider.copy() // ❸
val baz: Baz = f.randomProvider.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.randomProvider.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
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>(excludeName = "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()
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