Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix contain exactly verifier draft (#60) #3977

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -70,7 +70,11 @@ fun <T, C : Collection<T>> containExactly(
fun Throwable?.isDisallowedIterableComparisonFailure() =
this?.message?.startsWith(IterableEq.trigger) == true

val failureReason = eq(actual, expected, strictNumberEq = true)
val failureReason = if(verifier == null) {
eq(actual, expected, strictNumberEq = true)
} else {
matchCollectionsWithVerifier(actual, expected, verifier)
}

val missing = expected.filterNot { t ->
actual.any { verifier?.verify(it, t)?.areEqual() ?: (it == t) }
Expand All @@ -91,6 +95,11 @@ fun <T, C : Collection<T>> containExactly(
appendLine()
}

if (failureReason is CollectionMismatchWithCustomVerifier) {
append(failureReason.message)
appendLine()
}

appendMissingAndExtra(missing, extra)
appendLine()
}
Expand Down Expand Up @@ -129,6 +138,37 @@ fun <T, C : Collection<T>> containExactly(
}
}

internal fun<T> matchCollectionsWithVerifier(
actual: Collection<T>,
expected: Collection<T>,
verifier: Equality<T>
): CollectionMismatchWithCustomVerifier? {
val actualIterator = actual.iterator()
val expectedIterator = expected.iterator()
var index = 0
while (actualIterator.hasNext()) {
val actualElement = actualIterator.next()
if (expectedIterator.hasNext()) {
val expectedElement = expectedIterator.next()
val equalityResult = verifier.verify(actualElement, expectedElement)
if(!equalityResult.areEqual()) {
return CollectionMismatchWithCustomVerifier(
"Elements differ at index $index, expected: <${expectedElement.print().value}>, but was <${actualElement.print().value}>, ${equalityResult.details().explain()}"
)
}
} else {
return CollectionMismatchWithCustomVerifier("Actual has an element at index $index, expected is shorter")
}
index++
}
if (expectedIterator.hasNext()) {
return CollectionMismatchWithCustomVerifier("Expected has an element at index $index, actual is shorter")
}
return null
}

internal class CollectionMismatchWithCustomVerifier(message: String): Exception(message)

@JvmName("shouldNotContainExactly_iterable")
infix fun <T> Iterable<T>?.shouldNotContainExactly(expected: Iterable<T>) =
this?.toList() shouldNot containExactly(expected.toList())
Expand Down
Expand Up @@ -3,6 +3,8 @@ package com.sksamuel.kotest.matchers.collections
import io.kotest.assertions.shouldFailWithMessage
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.equals.Equality
import io.kotest.equals.EqualityResult
import io.kotest.matchers.collections.CountMismatch
import io.kotest.matchers.collections.containExactly
import io.kotest.matchers.collections.containExactlyInAnyOrder
Expand Down Expand Up @@ -30,6 +32,16 @@ import kotlin.time.Duration.Companion.seconds


class ShouldContainExactlyTest : WordSpec() {
private val caseInsensitiveStringEquality: Equality<String> = object : Equality<String> {
override fun name() = "Case Insensitive String Matcher"

override fun verify(actual: String, expected: String): EqualityResult {
return if (actual.uppercase() == expected.uppercase())
EqualityResult.equal(actual, expected, this)
else
EqualityResult.notEqual(actual, expected, this)
}
}

init {

Expand Down Expand Up @@ -279,6 +291,19 @@ class ShouldContainExactlyTest : WordSpec() {
""".trimMargin()
}

"pass with custom verifier" {
listOf("Apple", "ORANGE", "apple") should containExactly(
listOf("Apple", "orange", "APPLE"),
caseInsensitiveStringEquality
)
}

"fail with custom verifier" {
listOf("Apple", "ORANGE", "orange") shouldNot containExactly(
listOf("Apple", "orange", "APPLE"),
caseInsensitiveStringEquality
)
}
}

"containExactlyInAnyOrder" should {
Expand Down
@@ -0,0 +1,51 @@
package io.kotest.matchers.collections

import io.kotest.core.spec.style.StringSpec
import io.kotest.equals.Equality
import io.kotest.equals.EqualityResult
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith

class MatchCollectionsWithVerifierTest: StringSpec() {
private val caseInsensitiveStringEquality: Equality<String> = object : Equality<String> {
override fun name() = "Case Insensitive String Matcher"

override fun verify(actual: String, expected: String): EqualityResult {
return if (actual.uppercase() == expected.uppercase())
EqualityResult.equal(actual, expected, this)
else
EqualityResult.notEqual(actual, expected, this)
}
}

init {
"match" {
matchCollectionsWithVerifier(
listOf("Apple", "ORANGE", "apple"),
listOf("Apple", "orange", "APPLE"),
caseInsensitiveStringEquality
) shouldBe null
}
"actual is too long" {
matchCollectionsWithVerifier(
listOf("Apple", "ORANGE", "apple"),
listOf("Apple", "orange"),
caseInsensitiveStringEquality
) shouldBe CollectionMismatchWithCustomVerifier("Actual has an element at index 2, expected is shorter")
}
"expected is too long" {
matchCollectionsWithVerifier(
listOf("Apple", "ORANGE"),
listOf("Apple", "orange", "apple"),
caseInsensitiveStringEquality
) shouldBe CollectionMismatchWithCustomVerifier("Expected has an element at index 2, actual is shorter")
}
"elements mismatch at index 2" {
matchCollectionsWithVerifier(
listOf("Apple", "ORANGE", "orange"),
listOf("Apple", "orange", "apple"),
caseInsensitiveStringEquality
)!!.message shouldStartWith "Elements differ at index 2, expected: <\"apple\">, but was <\"orange\">, \"apple\" is not equal to \"orange\" by Case Insensitive String Matcher"
}
}
}