diff --git a/CHANGELOG.md b/CHANGELOG.md index 915ba2b4..377f9c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added `first` and `single` assertion for `Iterable` +- Added `flatExtracting` assertion for `Iterable` ## [0.25] 2021-09-12 diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt index ee685e68..7270b854 100644 --- a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt +++ b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt @@ -181,6 +181,19 @@ fun Assert>.extracting( actual.map { Triple(f1(it), f2(it), f3(it)) } } +/** + * Flat extracts a value from each item in the iterable, allowing you to assert on a flattened list of those values. + * + * ``` + * assertThat(people) + * .flatExtracting(Person::relatives) + * .contains("Sue", "Bob") + * ``` + */ +fun Assert>.flatExtracting(f1: (E) -> Iterable): Assert> = transform { actual -> + actual.flatMap(f1) +} + /** * Asserts on each item in the iterable, passing if none of the items pass. * The given lambda will be run for each item. diff --git a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt index 53a95f30..6852acd1 100644 --- a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt +++ b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt @@ -416,14 +416,14 @@ class IterableTest { } @Test fun pair_extracting_function_passes() { - assertThat(listOf(Thing("one", 1, '1'), Thing("two", 2, '2')) as Iterable) + assertThat(listOf(Thing("one", 1, listOf('1')), Thing("two", 2, listOf('2'))) as Iterable) .extracting(Thing::one, Thing::two) .containsExactly("one" to 1, "two" to 2) } @Test fun pair_extracting_function_fails() { val error = assertFails { - assertThat(listOf(Thing("one", 1, '1'), Thing("two", 2, '2')) as Iterable) + assertThat(listOf(Thing("one", 1, listOf('1')), Thing("two", 2, listOf('2'))) as Iterable) .extracting(Thing::one, Thing::two) .containsExactly("one" to 2, "two" to 1) } @@ -432,34 +432,55 @@ class IterableTest { | at index:0 expected:<("one", 2)> | at index:0 unexpected:<("one", 1)> | at index:1 expected:<("two", 1)> - | at index:1 unexpected:<("two", 2)> ([Thing(one=one, two=1, three=1), Thing(one=two, two=2, three=2)])""".trimMargin(), + | at index:1 unexpected:<("two", 2)> ([Thing(one=one, two=1, three=[1]), Thing(one=two, two=2, three=[2])])""".trimMargin(), error.message ) } @Test fun triple_extracting_function_passes() { - assertThat(listOf(Thing("one", 1, '1'), Thing("two", 2, '2')) as Iterable) + assertThat(listOf(Thing("one", 1, listOf('1')), Thing("two", 2, listOf('2'))) as Iterable) .extracting(Thing::one, Thing::two, Thing::three) - .containsExactly(Triple("one", 1, '1'), Triple("two", 2, '2')) + .containsExactly(Triple("one", 1, listOf('1')), Triple("two", 2, listOf('2'))) } @Test fun triple_extracting_function_fails() { val error = assertFails { - assertThat(listOf(Thing("one", 1, '1'), Thing("two", 2, '2')) as Iterable) + assertThat(listOf(Thing("one", 1, listOf('1')), Thing("two", 2, listOf('2'))) as Iterable) .extracting(Thing::one, Thing::two, Thing::three) - .containsExactly(Triple("one", 1, '2'), Triple("two", 2, '3')) + .containsExactly(Triple("one", 1, listOf('2')), Triple("two", 2, listOf('3'))) } assertEquals( - """expected to contain exactly:<[("one", 1, '2'), ("two", 2, '3')]> but was:<[("one", 1, '1'), ("two", 2, '2')]> - | at index:0 expected:<("one", 1, '2')> - | at index:0 unexpected:<("one", 1, '1')> - | at index:1 expected:<("two", 2, '3')> - | at index:1 unexpected:<("two", 2, '2')> ([Thing(one=one, two=1, three=1), Thing(one=two, two=2, three=2)])""".trimMargin(), + """expected to contain exactly:<[("one", 1, ['2']), ("two", 2, ['3'])]> but was:<[("one", 1, ['1']), ("two", 2, ['2'])]> + | at index:0 expected:<("one", 1, ['2'])> + | at index:0 unexpected:<("one", 1, ['1'])> + | at index:1 expected:<("two", 2, ['3'])> + | at index:1 unexpected:<("two", 2, ['2'])> ([Thing(one=one, two=1, three=[1]), Thing(one=two, two=2, three=[2])])""".trimMargin(), error.message ) } //region extracting + //region flatExtracting + @Test fun flat_extracting_function_passes() { + val thing = Thing("one", 2, listOf('A', 'B')) + assertThat(listOf(thing) as Iterable).flatExtracting { it.three }.containsExactly('A', 'B') + } + + @Test fun flat_extracting_function_fails() { + val thing = Thing("one", 2, listOf('A', 'B')) + val error = assertFails { + assertThat(listOf(thing) as Iterable).flatExtracting { it.three }.containsExactly('C', 'D') + } + assertEquals( + """expected to contain exactly:<['C', 'D']> but was:<['A', 'B']> + | at index:0 expected:<'C'> + | at index:0 unexpected:<'A'> + | at index:1 expected:<'D'> + | at index:1 unexpected:<'B'> ([Thing(one=one, two=2, three=[A, B])])""".trimMargin(), error.message + ) + } + //region flatExtracting + //region first @Test fun first_element_present_match_passes() { assertThat(listOf(1, 2)).first().isEqualTo(1) @@ -507,5 +528,5 @@ class IterableTest { } //endregion - data class Thing(val one: String, val two: Int, val three: Char) + data class Thing(val one: String, val two: Int, val three: List) } \ No newline at end of file