serpro69 / kotlin-faker

Port of a popular ruby faker gem written in kotlin. Generate realistically looking fake data such as names, addresses, banking details, and many more, that can be used for testing and data anonymization purposes.
https://serpro69.github.io/kotlin-faker/
MIT License
473 stars 44 forks source link

Add faker extension module for kotest Arbs #233

Closed serpro69 closed 7 months ago

serpro69 commented 7 months ago

Kotest Property-based Testing provides generators for some basic types, but testing more complex types would require creating custom generators. Kotlin-Faker could be of help here and generate both realistically looking data via various data providers, as well as generate custom complex types via randomClass provider.

Example use-case:

class KotestPropertyArbsTest : DescribeSpec({
    describe("Custom kotlin-faker Arbs") {
        it("should use Faker extension property") {
            val b = BooksFaker()
            forAll(b.arb.bible.quote()) { q: String -> q.isNotBlank() }
            val f = Faker()
            forAll(f.arb.address.city()) { q -> q.isNotBlank() }
            val e = EduFaker()
            forAll(e.arb.educator.campus()) { q -> q.isNotBlank() }
        }
        it("should use Arb.Companion extension property") {
            forAll(Arb.booksFaker.bible.quote()) { q: String -> q.isNotBlank() }
            forAll(Arb.faker.address.city()) { q: String -> q.isNotBlank() }
        }
        it("should generate random class instance") {
            forAll(Arb.randomClass.instance()) { foo: Foo -> foo.bar.s.isNotBlank() }
            forAll(
                Arb.randomClass.instance<Foo> {
                    typeGenerator { "hello faker" }
                },
            ) {
                it.bar.s == "hello faker"
            }
        }
        it("should generate person with address") {
            val f = Faker()
            val person: () -> Arb<Person> = {
                Arb.randomClass.instance<Person> {
                    namedParameterGenerator("name") { f.name.name() }
                    namedParameterGenerator("age") { f.random.nextInt(20, 30) }
                }
            }
            val address: () -> Arb<Address> = {
                Arb.randomClass.instance<Address> {
                    namedParameterGenerator("city") { f.address.city() }
                    namedParameterGenerator("streetName") { f.address.streetName() }
                    namedParameterGenerator("streetAddress") { f.address.streetAddress() }
                }
            }
            forAll(person(), address()) { p: Person, a: Address ->
                p.name.isNotBlank()
                p.age in 20..30
                a.city.isNotBlank()
                a.streetName.isNotBlank()
                a.streetAddress.isNotBlank()
            }
        }
    }
})

class Foo(val bar: Bar)

class Bar(val s: String)

class Person(val name: String, val age: Int)

class Address(val city: String, val streetName: String, val streetAddress: String)

// pseudo-generated code below this line
// core faker
val Arb.Companion.faker get() = ArbFaker(Faker())
val Faker.arb: ArbFaker get() = ArbFaker(this)

class ArbFaker(faker: Faker) {
    val address: ArbAddress by lazy { ArbAddress(faker.address) }
    val name: ArbName by lazy { ArbName(faker.name) }
}

class ArbAddress(private val address: Address) {
    fun city(): Arb<String> = arbitrary { address.city() }
}

class ArbName(private val name: Name) {
    fun name(): Arb<String> = arbitrary { name.name() }
}

// books faker
val Arb.Companion.booksFaker get() = ArbBooks(BooksFaker())
val BooksFaker.arb: ArbBooks get() = ArbBooks(this)

class ArbBooks(booksFaker: BooksFaker) {
    val bible: ArbBible by lazy { ArbBible(booksFaker.bible) }
}

class ArbBible(private val bible: Bible) {
    fun character(): Arb<String> = arbitrary { bible.character() }

    fun location(): Arb<String> = arbitrary { bible.location() }

    fun quote(): Arb<String> = arbitrary { bible.quote() }
}