diff --git a/CHANGELOG.md b/CHANGELOG.md index a47343b9..d90344b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Add version of the `single` function which takes a predicate for selecting the element from a collection. + ### Changed - renamed `prop` to `having` as part of effort to unify API naming, old name is deprecated. - renamed `suspendCall` to `having` as part of effort to unify API naming, old name is deprecated. diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt index 65861d3d..58211daa 100644 --- a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt +++ b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt @@ -331,3 +331,18 @@ fun > Assert.single(): Assert { } } } + +/** + * Asserts the iterable contains exactly one element matching the expected element, + * and returns an assert on that element. + */ +fun > Assert.single(predicate: (E) -> Boolean): Assert { + return transform(appendName("single", ".")) { iterable -> + val matching = iterable.filter(predicate) + when (matching.size) { + 1 -> matching.single() + 0 -> expected("to have single element matching predicate but none found") + else -> expected("to have single element matching predicate but has ${matching.size}: ${show(matching)}") + } + } +} diff --git a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt index 79ed523b..6ca401eb 100644 --- a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt +++ b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt @@ -599,30 +599,46 @@ class IterableTest { @Test fun single_single_element_match_passes() { assertThat(listOf(1)).single().isEqualTo(1) + assertThat(listOf(-1, 1, 0, -2)).single { it > 0 }.isEqualTo(1) } @Test fun single_single_element_mismatch_fails() { - val error = assertFailsWith { + val error1 = assertFailsWith { assertThat(listOf(1)).single().isEqualTo(2) } - assertEquals("expected [single]:<[2]> but was:<[1]> ([1])", error.message) + assertEquals("expected [single]:<[2]> but was:<[1]> ([1])", error1.message) + + val error2 = assertFailsWith { + assertThat(listOf(1)).single { it > 0 }.isEqualTo(2) + } + assertEquals("expected [single]:<[2]> but was:<[1]> ([1])", error2.message) } @Test fun single_no_element_fails() { - val error = assertFailsWith { + val error1 = assertFailsWith { assertThat(emptyList()).single().isEqualTo(1) } - assertEquals("expected to have single element but was empty", error.message) + assertEquals("expected to have single element but was empty", error1.message) + + val error2 = assertFailsWith { + assertThat(emptyList()).single { it != null }.isEqualTo(1) + } + assertEquals("expected to have single element matching predicate but none found", error2.message) } @Test fun single_multiple_fails() { - val error = assertFailsWith { + val error1 = assertFailsWith { assertThat(listOf(1, 2)).single().isEqualTo(1) } - assertEquals("expected to have single element but has 2: <[1, 2]>", error.message) + assertEquals("expected to have single element but has 2: <[1, 2]>", error1.message) + + val error2 = assertFailsWith { + assertThat(listOf(1, 2)).single { it > 0 }.isEqualTo(1) + } + assertEquals("expected to have single element matching predicate but has 2: <[1, 2]>", error2.message) } //endregion