https://kotlinlang.org logo
Title
e

Emil Kantis

06/09/2021, 8:04 PM
Inspectors have sort of eluded me.. My typical use-pattern when writing tests is to write my test and go
x should...
and look for a good assertion that does what I want it to, perhaps throwing in some word that fits. Would it make sense to just bake inspectors into the regular set of collector matchers and deprecate collection matchers which take a predicate in favour of ones that use matcher-blocks?
1
s

sam

06/09/2021, 8:07 PM
We probably could do some kind of unification yes, too many ways to do the same thing atm
e

Emil Kantis

06/09/2021, 8:07 PM
Could be renamed something like this perhaps?
forAll
->
shouldAll
which asserts every element passes the assertions
forNone
->
shouldContainNone
which asserts no element passes
forOne
->
shouldContainSingle
which asserts only a single element passed
forAtMostOne
->
shouldContainAtMostOne
which asserts that either 0 or 1 elements pass
forAtLeastOne
->
shouldContainAtLeastOne
which asserts that 1 or more elements passed
forAtLeast(k)
->
shouldContainAtLeast(k)
which is a generalization that k or more elements passed
forAtMost(k)
->
shouldContainAtMost(k)
which is a generalization that k or fewer elements passed
forAny
which is an alias for forAtLeastOne ->
shouldContainAtLeastOne
forSome
->
shouldContainSome
which asserts that between 1 and n-1 elements passed. Ie, if NONE pass or ALL pass then we consider that a failure.
forExactly(k)
->
shouldContain(k)
which is a generalization that exactly k elements passed. This is the basis for the implementation of the other methods
s

sam

06/09/2021, 8:13 PM
I'm not sure we should use the word should since they're not actually matchers per se ?
e

Emil Kantis

06/09/2021, 8:18 PM
hmm, not sure I understand the difference.. I sometimes do:
subject.should {
  it.name shouldBe "karl"
  it.age shouldBeGreaterThan 25
}
In this case we enable
val subjects = listOf(...)
subjects.shouldAll {
  it.age shouldBeInRange 18..65
  it.name shouldBeIn listOf("hans", "greta")
}
It feels like a natural analogue between single <-> collection assertions to me at least 🙂
s

sam

06/09/2021, 8:19 PM
hmmmm
subjects.shouldMatchAtMost(k) { }
or
subjects.forAtMost(k) { }
Is this a change that gains us much ?
e

Emil Kantis

06/09/2021, 8:21 PM
hard to argue, could improve discoverability but not sure
s

sam

06/09/2021, 8:22 PM
Sometimes the fact something has been like it is for 5 years is enough of a reason to keep it the way it is, unless there's a discernible reason to change it.
e

Emil Kantis

06/09/2021, 8:37 PM
s

sam

06/09/2021, 8:37 PM
agreed
Probably should stick to a format, or offer both
e

Emil Kantis

06/09/2021, 8:48 PM
I'll start a PR and explore it a bit, and if it doesn't sit right we can scrap it 🙂
s

sam

06/09/2021, 8:49 PM
Yeah ok nice 🙂
w

wasyl

06/09/2021, 8:59 PM
Personally I find myself wanting to write
someList shouldHaveSingleElement { it.label == "expected" }
, that is piggyback on the existing collection matchers, but pass another matcher as the expectation
s

sam

06/09/2021, 9:00 PM
so like
someList.shouldHaveSingleElement(::matcher) ?
w

wasyl

06/09/2021, 9:01 PM
Yep
s

sam

06/09/2021, 9:02 PM
isn't that just the same as
someList.forOne { a shouldDoSomething b }
w

wasyl

06/09/2021, 9:03 PM
I also didn’t know until this week that inspectors exist 😄 But wait, it’s not actually what I want, I see there’s
Collection<T>.shouldHaveSingleElement(p: (T) -> Boolean)
already. What I miss most often is a way to compare two lists with custom comparator
s

sam

06/09/2021, 9:04 PM
example of how you would use that if it existed ?
w

wasyl

06/09/2021, 9:06 PM
so like
data class Foo(val str: String, val callback: () -> Unit)
callback is usually different so I want to compare by
str
only. Right now we have
fun List<Foo>.toSnapshot() = "$str"

fooList.toSnapshot() shouldBe expectedFooList.toSnapshot()
I don’t know what I’d like, but something like
fooList.something(expectedList) { actual, expected -> actual.str == expected.str }
but I’m pretty tired and I don’t know if it makes sense anymore, the snapshot actually looks better now. So, disregard I suppose 😛
But yeah some matchers miss a
(T) -> Boolean
selector variant, like
fun <T, C : Collection<T>> C.shouldContain(t: T)
doesn’t have a
shouldContain { it % 2 == 0 }
variant like
shouldHaveSingleElement
does
s

sam

06/09/2021, 9:08 PM
we should definitely round out the missing blocks/predicates
e

Emil Kantis

06/09/2021, 9:09 PM
But like the issue I linked mentions, it's much nicer to be able to supply a block where you can use assertions than having to use a predicate, since we get better failure messages and can leverage all other assertions that exist on a single element 🙂
s

sam

06/09/2021, 9:10 PM
agreed
w

wasyl

06/09/2021, 9:10 PM
Yeah, also seems like a matcher is better than
(T) -> Boolean
too, since it’ll be more flexible. In fact, only a matcher would be required, and the basic
X shouldBe y
could be
x shouldBe eq(y)
, and the usual comparison would be just a handy helper
e

Emil Kantis

06/09/2021, 11:54 PM
something to look at, for now.. do you think it's a good idea to wrap the
runTests
and implement the matcher contract like this? way too late here now, but I'll setup a draft PR tomorrow to facilitate discussion
👍🏻 1
j

johisa

06/10/2021, 8:19 AM
Nice, I found Inspectors just by pure luck when poking around the API the other day, would be used way more if it followed the
should...
convention I believe