https://kotlinlang.org logo
#atrium
Title
i

Igor Akkerman

12/31/2019, 12:21 AM
Hi, I'm new to Atrium (and Kotlin) so bear with me. I have a collection of objects and I want to make assertions on its content using some of the objects' properties. What I currently have is not readable. Is there a cleaner way?
Copy code
data class Member(val name: String, val interest: String, val favoriteNumber: Int)
val members = listOf(Member("Me", "Kotlin", Random.nextInt()), Member("MyDog", "food", Random.nextInt()))
expect(members)
        .containsExactly(
                { feature { f(it::name) }.toBe("Me") ; feature { f(it::interest) }.toBe("Kotlin")},
                { feature { f(it::name) }.toBe("MyDog") ; feature { f(it::interest) }.toBe("food")}
        )
r

robstoll

01/06/2020, 6:42 AM
Hi Igor, in case you use this combination name/interest often, then I would suggest you add a corresponding assertion function (maybe you can come up with a better name):
Copy code
fun Expect<Member>.hasNameAndInterest(name: String, interest: String) =
    this.feature({ f(it::name) }, { toBe(name) })
        .feature({ f(it::interest) }) { toBe(interest) }
then it can be rewritten to:
Copy code
expect(members).containsExactly(
    { hasNameAndInterest("Me", "Kotlin") },
    { hasNameAndInterest("MyDog", "food") }
)
In case
toBe
was only an example and you want to perform other assertions on the properties, e.g.
startsWith("Kot")
then you could provide a shortcut for the properties:
Copy code
val Expect<Member>.name
    get() = name(this).getExpectOfFeature()

// allows to define an assertion group for the feature
fun Expect<Member>.name(assertionCreator: Expect<String>.() -> Unit) =
    name(this).addToInitial(assertionCreator)

private fun name(expect: Expect<Member>) = ExpectImpl.feature.property(expect, Member::name)
Whether this boilerplate is worth it depends on how many times you use the property IMO. Then you could use it as follows:
Copy code
expect(members).containsExactly(
    { name.toBe("Me") },
    { name { startsWith("Ro"); endsWith("t") } }
)
@Igor Akkerman I hope that helps, otherwise let me know
i

Igor Akkerman

01/06/2020, 11:45 AM
Hi Robert, thanks a lot for your help. I'm really a fan of the flexibility of Atrium and the expressiveness of the assertion code and especially of the output in case of test failure. Might there be a way on the side of the library to make the latter version require less boilerplate, generalizing assertions on properties? From AssertJ, I remember it allowing to specify fields to do comparisons on. It was using the field names as Strings, though. I noticed a tiny irregularity: While the assertion works correctly, in entry 1, the output shows "name" as "Me", while the object has "MyDog". Could the assertion have cached the value in a wrong way?
ch.tutteli.atrium.reporting.AtriumError: expect: [Member(name=Me, interest=Kotlin, favoriteNumber=1771517067), Member(name=MyDog, interest=food, favoriteNumber=-1574926733)]        (java.util.Arrays.ArrayList <1682681674>)
◆ contains only, in order: ✔️▶️ entry 0: Member(name=Me, interest=Kotlin, favoriteNumber=1771517067) (atriumtest.AtriumTest.Member <613298587>)  an entry which: » ▶️ name: "Me" <1446983876>  to be: "Me" <1446983876> » ▶️ interest: "Kotlin" <1042790962>  to be: "Kotlin" <1042790962> ✘ ▶️ entry 1: Member(name=MyDog, interest=food, favoriteNumber=-1574926733) (atriumtest.AtriumTest.Member <2130192211>)  an entry which: » ▶️ name: "Me" <1446983876>  to be: "MyDog" <990897274> » ▶️ interest: "Kotlin" <1042790962>  to be: "foodX" <539690370> ✔️▶️ size: 2 (kotlin.Int <593415583>)  to be: 2 (kotlin.Int <593415583>)
r

robstoll

01/06/2020, 11:47 AM
do you mean simplifying
feature { f(it::name) }
?
or are you talking about something in the output?
what would you like to see?
ah... forget what I just wrote 😄 I missed the
I noticed a tiny irregularity
i

Igor Akkerman

01/06/2020, 11:49 AM
You didn't miss it, I hit enter to quickly and edited the original message 😉
r

robstoll

01/06/2020, 11:49 AM
ah ok
i

Igor Akkerman

01/06/2020, 11:50 AM
Yes, I meant simplifying
feature { f(it::name) }
r

robstoll

01/06/2020, 11:50 AM
the irregularity is actually a bug I have on my radar for a longer time (planned for 0.10.0 now, see roadmap https://github.com/robstoll/atrium#0100). You are the first noticing/reporting it though
Atrium should not show the value at all in, it should be as follows:
Copy code
an entry which:
>> name:
  - to be: "My Dog"
i

Igor Akkerman

01/06/2020, 11:52 AM
Actually, it's better if it shows the actual value alongside the expected value, so you see the diff immediately
r

robstoll

01/06/2020, 11:52 AM
regarding simplifying
feature
it is mainly that complicated because Kotlin has unfortunately many bugs in the area of type inference and method overloading. See the list here https://github.com/robstoll/atrium#kotlin-bugs, would be nice if you can upvote some, so jetbrains invests a bit more time in this area
you are right, it could be shown for
in order only
, it would not make sense for
in any order
.
i

Igor Akkerman

01/06/2020, 11:54 AM
True
r

robstoll

01/06/2020, 11:55 AM
good input, I would have removed it for all functions, I'll keep that in mind
i

Igor Akkerman

01/06/2020, 11:55 AM
Great, thanks
r

robstoll

01/06/2020, 12:00 PM
you could also write
feature(Member::name)
in case you find that simpler
see also https://github.com/robstoll/atrium-roadmap/issues/21 maybe you can comment there regarding your use case
i

Igor Akkerman

01/06/2020, 12:02 PM
ah missed that one, that's perfectly readable/writable then
awesome
I believe
feature(Member::name)
is good enough, really. No idea if something like
containsExactly({Member::name toBe "Me"; Member::interest toBe "Kotlin"})
would be technically possible
To me, comparing data classes by showing their toString() diff is perfectly fine
9 Views