diff --git a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/duplicates.kt b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/duplicates.kt index 8a6229dcdec..f01157ee196 100644 --- a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/duplicates.kt +++ b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/duplicates.kt @@ -1,5 +1,6 @@ package io.kotest.matchers.collections +import io.kotest.assertions.print.print import io.kotest.matchers.Matcher import io.kotest.matchers.MatcherResult import io.kotest.matchers.should @@ -35,10 +36,18 @@ fun Collection.shouldNotContainDuplicates(): Collection { } fun containDuplicates() = object : Matcher> { - override fun test(value: Collection) = MatcherResult( - value.toSet().size < value.size, - { "Collection should contain duplicates" }, - { - "Collection should not contain duplicates" - }) + override fun test(value: Collection): MatcherResult { + val duplicates = value.duplicates() + return MatcherResult( + duplicates.isNotEmpty(), + { "Collection should contain duplicates" }, + { + "Collection should not contain duplicates, but has some: ${duplicates.print().value}" + }) + } } + +internal fun Collection.duplicates(): Collection = this.groupingBy { it } + .eachCount().entries + .filter { it.value > 1 } + .map { it.key } diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt index 5ff0f233422..6ad7e2a28b9 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt @@ -3,6 +3,7 @@ package com.sksamuel.kotest.matchers.collections import io.kotest.assertions.shouldFail import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow +import io.kotest.assertions.throwables.shouldThrowAny import io.kotest.assertions.withClue import io.kotest.core.spec.style.WordSpec import io.kotest.equals.Equality @@ -230,6 +231,12 @@ class CollectionMatchersTest : WordSpec() { listOf(1, 2, 3, 3).shouldContainDuplicates() listOf(1, 2, 3, 4).shouldNotContainDuplicates() } + + "print duplicates in message" { + shouldThrowAny { + listOf(1, 2, 3, 4, 2, 1) shouldNot containDuplicates() + }.shouldHaveMessage("Collection should not contain duplicates, but has some: [1, 2]") + } } "singleElement" should { diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/DuplicatesTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/DuplicatesTest.kt new file mode 100644 index 00000000000..4af6ead267c --- /dev/null +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/DuplicatesTest.kt @@ -0,0 +1,22 @@ +package com.sksamuel.kotest.matchers.collections + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.duplicates +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder + +class DuplicatesTest : StringSpec(){ + init { + "return empty list" { + listOf(1, 2, 3, 4, null).duplicates().shouldBeEmpty() + } + + "return not null duplicates" { + listOf(1, 2, 3, 4, 3, 2).duplicates() shouldContainExactlyInAnyOrder listOf(2, 3) + } + + "return null duplicates" { + listOf(1, 2, 3, null, 4, 3, null, 2).duplicates() shouldContainExactlyInAnyOrder listOf(2, 3, null) + } + } +} diff --git a/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt b/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt index 02f4c97f707..4de325f9fb2 100644 --- a/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt +++ b/kotest-property/src/jvmMain/kotlin/io/kotest/property/arbitrary/targetDefaultForClassName.kt @@ -1,6 +1,5 @@ package io.kotest.property.arbitrary -import io.kotest.mpp.bestName import io.kotest.property.Arb import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -12,16 +11,14 @@ import java.time.LocalDateTime import java.time.LocalTime import java.time.OffsetDateTime import java.time.Period +import java.time.Year import java.time.YearMonth import java.time.ZonedDateTime import java.util.Date import kotlin.reflect.KClass -import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.javaType -import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.typeOf @Suppress("UNCHECKED_CAST") @@ -40,6 +37,7 @@ fun targetDefaultForType( typeOf(), typeOf() -> Arb.localDateTime() typeOf(), typeOf() -> Arb.localTime() typeOf(), typeOf() -> Arb.period() + typeOf(), typeOf() -> Arb.year() typeOf(), typeOf() -> Arb.yearMonth() typeOf(), typeOf() -> Arb.zonedDateTime() typeOf(), typeOf() -> Arb.offsetDateTime() diff --git a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt index dea51c5a90c..0b73da46cfa 100644 --- a/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt +++ b/kotest-property/src/jvmTest/kotlin/com/sksamuel/kotest/property/arbitrary/ReflectiveBindTest.kt @@ -23,6 +23,7 @@ import java.time.LocalDateTime import java.time.LocalTime import java.time.OffsetDateTime import java.time.Period +import java.time.Year import java.time.YearMonth import java.time.ZonedDateTime import kotlin.reflect.KClass @@ -100,9 +101,10 @@ class ReflectiveBindTest : StringSpec( val b: LocalDateTime, val c: LocalTime, val d: Period, - val e: YearMonth, - val f: OffsetDateTime, - val g: ZonedDateTime + val e: Year, + val f: YearMonth, + val g: OffsetDateTime, + val h: ZonedDateTime ) val arb = Arb.bind() @@ -115,9 +117,10 @@ class ReflectiveBindTest : StringSpec( val b: LocalDateTime?, val c: LocalTime?, val d: Period?, - val e: YearMonth?, - val f: OffsetDateTime?, - val g: ZonedDateTime? + val e: Year?, + val f: YearMonth?, + val g: OffsetDateTime?, + val h: ZonedDateTime? ) val arb = Arb.bind()