Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Add version of the `single` function which takes a predicate for selecting the element from a collection.
- Add version of the `single` function which takes a predicate for selecting the element from an `Iterable`.

What about this?


### 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.
Expand Down
15 changes: 15 additions & 0 deletions assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,18 @@ fun <E, T : Iterable<E>> Assert<T>.single(): Assert<E> {
}
}
}

/**
* Asserts the iterable contains exactly one element matching the expected element,
* and returns an assert on that element.
*/
inline fun <E, T : Iterable<E>> Assert<T>.single(predicate: (E) -> Boolean): Assert<E> {
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)}")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -599,30 +599,46 @@ class IterableTest {
@Test
fun single_single_element_match_passes() {
assertThat(listOf(1)).single().isEqualTo(1)
assertThat(listOf(1)).single { it > 0 }.isEqualTo(1)
}

@Test
fun single_single_element_mismatch_fails() {
val error = assertFailsWith<AssertionError> {
val error1 = assertFailsWith<AssertionError> {
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<AssertionError> {
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<AssertionError> {
val error1 = assertFailsWith<AssertionError> {
assertThat(emptyList<Any?>()).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<AssertionError> {
assertThat(emptyList<Any?>()).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<AssertionError> {
val error1 = assertFailsWith<AssertionError> {
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<AssertionError> {
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

Expand Down