Kotlin DSL
Kotlin DSL
Use krandom-kotlin-dsl when Kotlin tests need compact object-generation rules without dropping into Java builders.
Dependency
repositories {
mavenCentral()
}
dependencies {
testImplementation("io.github.frikit:krandom-kotlin-dsl:2.10.0")
}
Generate One Object
import io.github.frikit.krandom.dsl.krandom
val user = krandom<UserDto> {
config { seed(42L) }
rule(UserDto::firstName) { "Ada" }
rule(UserDto::email) { "ada@example.test" }
}
Typed Rules and Validation
Prefer property references over string names: they survive renames, and the compiler checks that the rule value matches the property type. Use the string form only for fields that cannot be referenced as Kotlin properties.
Rules are validated before generation:
- registering two rules for the same field or the same type fails immediately;
- a rule or exclusion naming a field that does not exist on the target class fails when the generator is built, listing the known field names;
- a rule or exclusion on a computed (
val x get() = ...) or delegated (by lazy) property fails when the generator is built: such properties have no backing field and are not primary-constructor parameters, so nothing could apply to them.
val account = krandom<Account> {
rule(Account::owner) { "grace" } // type-safe; survives refactoring
exclude(Account::internalNotes) // type-safe exclusion
}
Field rules and exclusions are scoped to the generated (root) type (2.6+): exclude(Customer::name)
leaves Customer.name at its initial value while a nested Company.name is still generated. A field
inherited from a superclass is matched through that superclass field, so it also applies to other
subclasses of it in the same object graph. Use ruleForType for type-wide rules and the Java
ObjectFaker for nested-path rules. Exclusions on immutable data classes built through the primary
constructor are not yet honoured by the constructor adapter; exclude those fields on mutable types
or register a rule instead.
Generate Lists
import io.github.frikit.krandom.dsl.krandomList
val users = krandomList<UserDto>(10) {
rule("country") { "United States" }
}
Reusable Generators
import io.github.frikit.krandom.dsl.krandomGenerator
val users = krandomGenerator<UserDto> {
ruleForType<String> { "fixed" }
}
val one = users.generate()
val many = users.generateList(5)
Rules are backed by GeneratorConfig object overrides, so the DSL shares the same defaults, seeding, locale, and object-generation behavior as the Java API.
That includes unique fields. A rule or ruleForType may repeat a constant on every object, as above. To request a field whose values must not repeat, use config { objectUniqueFields(...) }, which replaces the names that are unique by default. A constant on a requested field fails on a second object from the same generator.
Java fixture API and Kotlin DSL scope
The Kotlin DSL is the concise, typed front end for ordinary root-property fixtures. It deliberately
does not claim every Java ObjectFaker feature. Choose the Java fixture API when a test needs the
broader rule-composition surface.
| Capability | Java ObjectFaker |
Kotlin DSL |
|---|---|---|
| Root property rules | String, accessor, and PropertyPath rules |
Property-reference and string rules |
| Nested-path rules | Supported | Use Java ObjectFaker |
| Contextual and dependent rules | Supported | Use Java ObjectFaker |
Include, profiles, post-processing, and populate |
Supported | Use Java ObjectFaker |
| Immutable reusable models | ObjectModel<T> |
Use Java ObjectModel |
| Kotlin primary constructors | Via the Kotlin DSL module | Via the Kotlin DSL module |
| Initialized properties | A field rule replaces them; other values keep them unless objectOverrideDefaultInitialization(true) |
Rules, ruleForType values, and generated values all replace them |
In both APIs, an explicit field rule takes precedence over a type rule and generated defaults.
The DSL validates duplicate field/type rules immediately (a Java ObjectFaker rule instead replaces
an earlier rule for the same path) and unknown root field names when building.
It supports neither value classes nor sealed/abstract types without a concrete type override.
These boundaries are intentional: they keep the Kotlin adapter small while the Java API remains the
single place for complex fixture composition.
Immutable Kotlin classes
krandom-kotlin-dsl includes Kotlin reflection and constructs immutable Kotlin values through their
primary constructor. No extra kotlin-reflect declaration is needed.
data class UserFixture(
val name: String,
val email: String,
val roles: List<Role>,
val source: String = "test"
)
data class Role(val code: String)
val user = krandom<UserFixture> {
rule("name") { "Ada Lovelace" }
rule("email") { "ada@example.test" }
}
check(user.source == "test") // optional parameters retain their Kotlin default
The primary-constructor adapter uses the normal kRandom resolver for each parameter, so field/type
rules, nested generic containers, semantic values, deterministic seeds, and Jakarta Validation
annotations all apply. Both @param: and @field: constraint targets are recognized. An explicit
field or type rule wins over an optional parameter default.
Kotlin object singletons return their one instance. Value classes and sealed/abstract Kotlin
types are rejected before returning a value; use a concrete type or explicit type override. A
core-only application also rejects immutable Kotlin types with a message naming
krandom-kotlin-dsl, rather than allocating an invalid instance.
The adapter always invokes the primary constructor; secondary constructors are never selected. Delegated properties initialize normally as part of that construction. A required recursive immutable graph fails contextually rather than returning a value with a runtime null.
Immutable Data Classes, Generics, Defaults, and Constraints
// Immutable data classes construct through the primary constructor
data class Account(val owner: String, val balance: Long)
val account = krandom<Account> { rule(Account::owner) { "grace" } }
// Nested generics keep their declared element types
data class Ledger(val entries: List<Map<String, Long>>)
val ledger = krandom<Ledger>()
// Kotlin default parameter values are honored unless a rule overrides them
data class Invoice(val currency: String = "EUR", val amount: Long)
val invoice = krandom<Invoice>() // currency == "EUR"
// Jakarta Validation constraints on constructor parameters are satisfied
data class Customer(@field:jakarta.validation.constraints.Email val contact: String)
val customer = krandom<Customer>()
Standalone Configuration and Replay Recipes
val config = krandomConfig {
seed("checkout-fixtures") // textual seeds use the shared derivation contract
constructionPolicy(ObjectConstructionPolicy.SAFE)
}
val recipe = config.generationRecipe.orElseThrow().serialize() // portable replay recipe
The DSL matches the Java builder defaults with one intentional, documented difference:
objectOverrideDefaultInitialization is enabled, so ruleForType values and generated values also
replace property initializers. A rule(...) replaces its property’s initializer, as field rules do in
the Java API.
Opt-in independent streams (2.3+)
import io.github.frikit.krandom.generator.`object`.ObjectFieldStreamPolicy
val users = krandomList<User>(5) {
config {
seed(42L)
objectFieldStreamPolicy(ObjectFieldStreamPolicy.INDEPENDENT)
}
rule(User::name) { "Ada" }
}
The Kotlin option uses the same core policy as Java. Existing configurations remain LEGACY. For direct configuration
consumers, krandomConfig { seed(42L) }.snapshotClock() captures an explicit replay clock.