Hi all! :wave: Just wanted to share my testing lib...
# test
s
Hi all! πŸ‘‹ Just wanted to share my testing lib for generating fake data - kotlin-faker. Unfortunately only JVM is supported for now, but hopefully multiplatform will come soon 😏 (well... as soon as I get some extra time to learn it, or someone contributes πŸ˜„ ) And as a side bonus, it also includes property-testing extension for my favorite kotlin testing lib - kotest. Hope someone finds it useful; and if not - that's also OK πŸ™‚ Cheers 🀜 πŸ€›
c
If you're based on Kotest, any reason you're using your own infrastructure and not relying on
Arb
and
Exhaustive
directly? I see there is https://serpro69.github.io/kotlin-faker/extensions/kotest-property-extension/, but I'd rather not add KSP etc just to generate test data.
s
Hey @CLOVIS . The kotest bits, that's just an extension that adds Arb support for fake data generators. It's optional and not part of the core library. KSP can be added as test dependencies, it doesn't need to be on the "main" classpath. Well, yeah, you still need to add the ksp plugin though. But I'm not really sure there's a way around that.
Adding an
Arb
extension for each data generator... I think that's too much code maintenance πŸ˜„ hence why I thought initially to just generate these things. But I'd like to hear more about why you think the current solution via KSP isn't good enough. It can certainly be changed if something else would improve the user experience.
c
What code does KSP generate for each generator?
s
I guess one way to completely avoid dependency on KSP would be to commit the generated code, instead of providing the extension with bundled KSP stuff πŸ€” That way I won't need to think about keeping things up to date every time something changes - I can just re-generate. And at the same time there's no KSP anymore that is exposed to the client
Basically something like this
Copy code
public val Faker.arb: ArbFaker // β‘ 
    get() = ArbFaker(this)
public val Arb.Companion.faker: ArbFaker // β‘‘
    get() = io.github.serpro69.kfaker.ArbFaker(Faker())

public val BooksFaker.arb: ArbBooksFaker // β‘ 
    get() = ArbBooksFaker(this)
public val Arb.Companion.booksFaker: ArbBooksFaker // β‘‘
    get() = io.github.serpro69.kfaker.books.ArbBooksFaker(BooksFaker())

public class ArbFaker(private val faker: Faker) { // β‘’
    public val address: ArbAddress by lazy { ArbAddress(faker.address) }

    public val color: ArbColor by lazy { ArbColor(faker.color) }

    public val currency: ArbCurrency by lazy { ArbCurrency(faker.currency) }

    // ...
}

public class ArbAddress internal constructor(private val address: Address) { // β‘£
    public fun city(): Arb<String> = arbitrary { address.city() }

    public fun country(): Arb<String> = arbitrary { address.country() }

    // ...
}
c
Isn't this everything that's needed?
Copy code
public val Faker.arb: ArbFaker // β‘ 
    get() = ArbFaker(this)
public val Arb.Companion.faker: ArbFaker // β‘‘
    get() = io.github.serpro69.kfaker.ArbFaker(Faker())
Then we can just use
Arb.faker.bank.name()
no?
s
Hmm. I think that
name()
function itself must return an
Arb
instance, right? So then it needs to be wrapped e.g. in an
arbitrary { }
Copy code
public class ArbAddress internal constructor(private val address: Address) {
    public fun city(): Arb<String> = arbitrary { address.city() }

    public fun country(): Arb<String> = arbitrary { address.country() }
}
Then you can do e.g.
Copy code
forAll(f.arb.address.city()) { q ->
  q.isNotBlank()
}
Address
here is part of the core functionality (no KSP) The
ArbAddress
class from above is generated by the extension lib via KSP
c
So there isn't really a type for each possible data generator? It's not possible to hold a generator in a variable?
If you had a
Generator
type, you could have something like
Arb.of(faker.bank.name)
be the conversion function.
s
yeah, I mean, you can definitely do that and define your own stuff πŸ™‚
so it's just "convenience extensions" that provides you a ready-to-use arb for all available functionality if you only generate a few things, it probably doesn't make much sense to use the extension since you can just define a few arbs on your own but if you use many generators and from various fakers, then it could make sense to use the extension
but I do like your point that depending on KSP might not be ideal I'll explore the possibility of committing generated code instead of using on-demand generation via KSP
maybe don't even need to commit it, just include it in the published artifact
c
Yeah, I think you can just publish the module without committing it
Even then, that seems like a lot of code that is essentially glue. Since it's just for tests, it's not that important, but still.
s
glue - exactly why I didn't want to write it by hand πŸ˜„
c
Ahah, you're definitely right about that! But I think there should a simpler way than going through all this trouble
s
Oh, I'm sure there is. But I also wanted to try KSP, and this seemed like a good opportunity heh
πŸ‘ 1
c
Your generators are always functions that return strings, right? So maybe something as simple as
Copy code
fun <T> Arb.Companion.of(block: () -> T) = arbitrary { block() }
usage:
Copy code
Arb.of(fake.bank::name)
?
πŸ‘ 1
s
Yeah, looks like this should do the trick πŸ‘
Took your suggestion and killed a bunch of code: https://github.com/serpro69/kotlin-faker/pull/246 I love it πŸ˜„ Thanks for your inputs πŸ™‚
πŸ™Œ 1