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 baz: Baz = faker.randomProvider.randomClassInstance {
    typeGenerator<UUID> { UUID.fromString("00000000-0000-0000-0000-000000000000") }
    typeGenerator<Int> { 0 }
    namedParameterGenerator("relatedUuid") { UUID.fromString("11111111-1111-1111-1111-111111111111") }
}


So for each instance of Baz the following will be true:

baz.id == 0
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 over constructorFilterStrategy
  • both can be specified at the same time, though in most cases it probably makes more sense to use fallbackStrategy with constructorParamSize 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 classes Foo, Bar, and Baz will use the same random instance configuration settings when instances of those classes are created in FooBarBaz 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