replace `property` and returnValueOf with `feature` or similar
robstoll opened this issue · 2 comments
Platform (JVM and/or JS): JVM/JS
Code related feature
setup
data class Person(val firstName: String, val lastName: String) {
fun fullName() = "$firstName $lastName"
fun nickname(includeLastName: Boolean) = when(includeLastName){
false -> "Mr. $firstName"
true -> "$firstName aka. $lastName"
}
}
val person = Person("Robert", "Stoll")
feature as such
assert(person) {
feature(Person::fullName).contains("treboR", "llotS")
feature(Person::nickname, false).toBe("Robert aka. Stoll")
}
We could furthermore consider to add featureRef
as alternative where one can use a bounded reference:
assert(person).featureRef { subject::firstName }.toBe("robert")
assert(person).featureRef({ subjectPerson::nickname }, false).toBe("Robert aka. Stoll")
Yet, considering the problems we still encounter due to Kotlins problems with overloads involving KFunction types, I am not sure if it is really a good idea to allow bounded reference.
Alternatively we could also go with a Pair<String, T>
:
assert(person)
.feature{ "name" to name }
But that involves quite a bit of boilerplate. On the other hand it would allow mapping over multiple levels:
assert(person)
.feature{ "name.first()" to name.first() }
We could reduce the boilerplate for JS by using eval but eval is evil and we would loose type safety so probably not a good idea.
The good thing about map` would be that we hide the implementation detail whether firstName is actually a property or a getter. I stumble over this problematic from time to time when I deal with Java Code where Kotlin provides property syntax even for getters. But that's not the case for function references. So the following is the result which is... not so nice:
val person = Person("hello")
person.name //calling the getter via property syntax
assert(person).property(Person::name).toBe(...) //fails because it is not really a property but the method `getName`
assert(person).property(Person::getName).toBe(...) // fails as well, because it is a method not a property
assert(person).returnValueOf(Person::getName).toBe(...) // that's ok
We cannot get rid of the confusion that Person
does not have a name
property, but we can at least lower the confusion by providing one single method.
assert(person).feature(Person::getName)
Care has to be taken due to current bugs concerning overloading in Kotlin.
I am no longer sure if map
is a good choice. We do not map to the thing we return but use it to create a feature assertions.. Maybe feature
, as I intended to name it at the beginning, is better.
To workaround the kotlin bug concerning multiple overloads with function type we could introduce featureNullable
for nullable features. Then feature
could be define as
fun <T : Any, TFeature : Any> Assert<T>.feature(provider: FeatureProvider<T>.() -> Assert<TFeature>, assertionCreator: Assert<T>.() -> Unit): Assert<T>
where FeatureProvider in turn provides helper functions to construct features out of properties and functions. The only problem here, Kotlin has also bugs concerning function reference of overloaded functions and expecting KProperty/KFunction :(
We could start off with p
for property and f
for function (similar to property
and returnValueOf
which we have currently). The usage would then look something like:
data class Person(val firstName: String, val lastName: String, val nickName: String?)
assert(Person("robert", "stoll", null))
.feature({ p(it::firstName) }) { toBe("hello") }
.featureNullable({ p(it::nickName) }) { notToBeNullBut("robstoll") }
.feature({ f(it::name) }) { startsWith("robert") }
or we could start of with r
(for reference) people would only need to use p
and f
in case of a problem