Add Iterable assertion builder with constraint-based order matching
I've come across a few test scenarios where I want to assert ordering on an iterable, but contains, containsExactly, and containsExactlyInAnyOrder don't quite cut it. Two particular cases that come to mind are asserting lifecycle callbacks and asserting connection state. What they have in common is that some aspects of their order matter, but not all.
I've generally gone about solving these cases by either mangling the result list before asserting on it (e.g. filtering or reordering elements), or creating a combinatorial explosion of valid results and asserting that the result is one of them. Both leave my tests a lot uglier than I'd like.
This is my take on an assertion builder for iterables that can assert fuzzy ordering rules. The API is a block in which the user asserts elements in the subject, and constraints for those elements' positions relative to other elements.
Here's an example of its usage, illustrating my most recent use case:
enum class LifecycleCall { Created, Started, Stopped, Destroyed, Attached, Detached }
/**
* Assert that:
* - Created comes first
* - Destroyed comes last
* - In between Created and Destroyed, we get Started and Stopped
* - Attached comes after Created, but it doesn't matter if it's before or after Started
* - Detached comes before Destroyed, and after Attached,
* but it doesn't matter if it's before or after Stopped
*/
@Test
fun properLifecycleCallsAfterStartAndStop() {
val result = thingWithLifecycle.collectLifecycleCalls()
expectThat(result)
.containsWithOrderingConstraints {
expect(Created).first()
expect(Started)
.after(Created)
expect(Attached)
.before(Stopped)
// About to stop
expect(Detached)
.after(Attached)
expect(Stopped)
.after(Started)
expect(Destroyed).last()
}
}
I realize this is a rather opinionated addition to the assertion library, so if this isn't a good fit, feel free to close.
That's neat. I dig it.