Closed artdk closed 8 months ago
Hi @artdk ,
I'm always interested in something that brings value :) but would like to first understand your use-case.
Do you actually want to generate an instance of Instant
via randomClassInstance
function? Or is instant a property in another class?
Because if it's the former, then it's not really going to be "random instance" of Instant
as such, it will always return Instant.now()
(or whatever you configure via typeGenerator
). So why not just call Instant.now()
wherever you need an instance of it, instead of faker.randomClassInstance<Instant>()
? The result will be the same.
PS: I do get that you're using instant as an example here, but I'm not really seeing the use-case. So maybe you could elaborate on what you're trying to achieve.
This change is now in master and should be available in latest snapshot version after the build completes. I want to give it a bit more thought before getting this into an RC version, but feel free to try out the snapshot and share your feedback, if you have any. Thanks for suggesting this feature :)
Sorry for the delay,
The most pressing use case is when testing generic functions. For example we have an inline fun <reified T: Any> myFun<T>(p0: T): Boolean
which contains logic using the values of member variables which may or may not exist in p0; if we define this highly simplified test
@Test
fun myTest() {
testClass(Foo::class, true)
testClass(Instant::class, true)
testClass(Bar::class, false)
...
}
private inline fun <reified T: Any> testClass(klass: KClass<T>, expectedMyFunValue: Boolean) {
val testVal = faker.randomProvider.randomClassInstance<T>()
myFun(testVal) shouldBe expectedMyFunValue
}
This allows us to test myFun against a number of different types, but if one of those types has no public constructors then it cannot be tested using this generic method and instead we must provide an alternative generator for those specific cases
The other benefit here is consistency and control; for example in a given domain we have constraints on Instant, so we introduce a function randomConstrainedInstant()
to generate Instants that fit these constraints; if we then want to test a function myFun(p0: Foo, p1: Instant, p2: Foo): Bar
we can define the test variables as:
val p0 = faker.randomClassInstance<Foo>()
val p1 = randomConstrainedInstant()
val p2 = randomConstrainedInstant()
val result = myFun(p0, p1, p2)
// assert on result comparing to p0, p1, and p2
This is much more consistent if we can just use faker.randomClassInstance<Instant>()
for p1 and p2, and doesnt require that every developer has to remember all the cases where a a random object cannot be generated and all the exceptional random object generation functions that fill those gaps
I hope this helps elaborate on some of the motivation and cases that inspired this, and thanks for being so receptive to such a corner-case feature!
Thanks for the examples. I think this is a pretty valid use-case.
When generating a random object of type T via the
randomProvider.randomClassInstance<T>()
method, if a typeGenerator is defined for T then it would be helpful ifrandomClassInstance
simply used that generator instead of ignoring itExample
I want to generate a random:
Instant
(which does not have a public constructor) using a configured typeGeneratorIn this case
f.randomProvider.randomClassInstance<Instant>()
throwsjava.util.NoSuchElementException: No suitable constructor found for class java.time.Instant
because the implementation ofrandomClassInstance<Instant>()
only callsKClass<T>.predefinedTypeOrNull
for the types of constructor args (of which there are none in the case ofInstant
)This is not an issue when the the type of the predefined generator is not the type specified in
randomClassInstance<Instant>()
, for example if I definedata class Foo(val instant: Instant)
, thenf.randomProvider.randomClassInstance<Foo>()
generates correctlyPossible solution
I propose adding a line to the beginning of
RandomClassProvider
'sKClass<T>.randomClassInstance(config: RandomProviderConfig)
method that attempts to get a random instance from the predefined generators, and then return if the predefined generators return something. I am not sure how this would be best implemented, as the generators are designed to work with KParameters. One kinda hacky idea is to define something like:which would allow
val predefinedInstance = predefinedTypeOrNull(config, toParameterInfo()) as? T?
above line 105 of RandomClassProvider.kt and then the return statement on line 117 could becomereturn predefinedInstance ?: defaultInstance ?: objectInstance ?: run {
Is this a change you would be interested in? And if so you have any suggestions on how best it could be implemented?