willowtreeapps / assertk

assertions for kotlin inspired by assertj
MIT License
757 stars 84 forks source link

Discussion for context/name String parameters #535

Open grodin opened 3 months ago

grodin commented 3 months ago

There are a number of places in the API where it would be good to add a String parameter to name something or add some context. Some quick examples from existing open issues:

It seems like there are two main threads to these:

  1. API consistency (e.g. eachHaving() should have a name param to match having())
  2. Adding context to assertions (e.g. #526, )

I thought it would be useful to open an issue to kind of unify discussion around these ideas.

It would potentially be useful to go through the whole API and try to find the places where a string parameter seems to be wanted and try to design a consistent approach, rather than dealing with each request in an ad-hoc way.

Point 1. seems fairly easy to address, so I'll try to start collecting instances of that.

evant commented 3 months ago

Agreed that this could use some work. I'm going to try to explain what's there now as a starting point, which is sadly under-documented. Note: the examples below will be using some of the api's directly are usually would be implemented inside an assertion.

name

You can pass a name to assertions. They show up at the beginning and may built in assertions will append to it. Note: the api for this is fairly manual, i.e. you need to explicitly append to it when chaining, otherwise it'll only show the name you pass in.

ex:

assertThat(outer, name = "outer").assertThat(inner, name = "inner").someAssertion()
// -> [inner] assertion failed

assertThat(outer, name = "outer").run { assertThat(inner, name = appendName("inner", separator = ".")) }.someAssertion()
// -> [outer.inner] assertion failed

many built-in assertions will append to this, ex: prop does appendName(name, separator = ".") and index does appendName("[$index]")

context

This is something that's implicitly tracked and is appended to the end of the assertion. The purpose for this to know what you were originally asserting on through chaining. The logic here is that if you aren't chaining nothing will be shown, if you are, the outer subject will be carried through and appended to the message.

assertThat(outer).someAssertion()
// -> assertion failed

assertThat(outer).assertThat(inner).someAssertion()
// -> assertion failed (outer)

For completeness, you can control how this value is rendered with displayActual. The intention for this was for cases where you subject did not have a useful toString() implementation, but https://github.com/willowtreeapps/assertk/issues/513 correctly points out the behavior of this parameter is quite confusing.

assertThat(outer, displayActual = { "changed" }).someAssertion()
// -> assertion failed

assertThat(outer, displayActual = { "changed" }).assertThat(inner).someAssertion()
// -> assertion failed (changed)
dalewking commented 3 months ago

In assertJ they have something like this:

    assertThat(5 + 19)
        .describedAs("The sum of 5 and 9")
        .isEqualTo(14)

I think there is a library I have used that instead of adding the description to the assert it added it to the value with a wrapper class:

    val actual = (5 + 9).describedAs("The sum of 5 and 9")

    assertThat(actual).isEqualTo(14)