Skip to content

Commit

Permalink
add similarity search to shouldContain (#3906)
Browse files Browse the repository at this point in the history
Co-authored-by: Sam <sam@sksamuel.com>
Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>
  • Loading branch information
3 people committed Mar 29, 2024
1 parent 2d62856 commit aa0813b
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 8 deletions.
Expand Up @@ -6,6 +6,7 @@ import io.kotest.matchers.Matcher
import io.kotest.matchers.MatcherResult
import io.kotest.matchers.should
import io.kotest.matchers.shouldNot
import io.kotest.similarity.possibleMatchesDescription

// Infix
infix fun <T> Iterable<T>.shouldNotContain(t: T): Iterable<T> = shouldNotContain(t, Equality.default())
Expand Down Expand Up @@ -33,12 +34,19 @@ fun <T> Array<T>.shouldContain(t: T, comparator: Equality<T>): Array<T> = apply

// Matcher
fun <T, C : Collection<T>> contain(t: T, verifier: Equality<T> = Equality.default()) = object : Matcher<C> {
override fun test(value: C) = MatcherResult(
value.any { verifier.verify(it, t).areEqual() },
{
"Collection should contain element ${t.print().value} based on ${verifier.name()}; " +
"but the collection is ${value.print().value}"
},
{ "Collection should not contain element ${t.print().value} based on ${verifier.name()}" }
)
override fun test(value: C) : MatcherResult {
val passed = value.any { verifier.verify(it, t).areEqual() }
val possibleMatches = if(!passed && (verifier.name() == Equality.default<T>().name())) {
val candidates = possibleMatchesDescription(value.toSet(), t)
if(candidates.isEmpty()) "" else "\nPossibleMatches:$candidates"
} else ""
return MatcherResult(
passed,
{
"Collection should contain element ${t.print().value} based on ${verifier.name()}; " +
"but the collection is ${value.print().value}$possibleMatches"
},
{ "Collection should not contain element ${t.print().value} based on ${verifier.name()}" }
)
}
}
@@ -1,8 +1,13 @@
package com.sksamuel.kotest.matchers.collections

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.throwables.shouldThrowAny
import io.kotest.core.spec.style.WordSpec
import io.kotest.equals.Equality
import io.kotest.equals.EqualityResult
import io.kotest.equals.ReflectionUsingFieldsEquality
import io.kotest.equals.SimpleEqualityResult
import io.kotest.equals.SimpleEqualityResultDetail
import io.kotest.equals.types.byObjectEquality
import io.kotest.matchers.collections.contain
import io.kotest.matchers.collections.shouldContain
Expand Down Expand Up @@ -38,6 +43,44 @@ class ShouldContainTest : WordSpec({
col shouldContain (2)
}

"find similar element" {
shouldThrowAny {
listOf(sweetGreenApple, sweetGreenPear) shouldContain (sweetRedApple)
}.shouldHaveMessage(
"""
|Collection should contain element Fruit(name=apple, color=red, taste=sweet) based on object equality; but the collection is [Fruit(name=apple, color=green, taste=sweet), Fruit(name=pear, color=green, taste=sweet)]
|PossibleMatches:
| expected: Fruit(name=apple, color=green, taste=sweet),
| but was: Fruit(name=apple, color=red, taste=sweet),
| The following fields did not match:
| "color" expected: <"green">, but was: <"red">
""".trimMargin()
)
}

"add nothing to output if no similar elements found" {
shouldThrowAny {
listOf(sweetGreenApple, sweetGreenPear) should contain (sourYellowLemon)
}.shouldHaveMessage(
"""
|Collection should contain element Fruit(name=lemon, color=yellow, taste=sour) based on object equality; but the collection is [Fruit(name=apple, color=green, taste=sweet), Fruit(name=pear, color=green, taste=sweet)]
""".trimMargin()
)
}

"add nothing to output if custom comparator is used" {
shouldThrowAny {
listOf(sweetGreenApple, sweetGreenPear).shouldContain(
sourYellowLemon,
comparator = FruitEquality
)
}.shouldHaveMessage(
"""
|Collection should contain element Fruit(name=lemon, color=yellow, taste=sour) based on fruit equality; but the collection is [Fruit(name=apple, color=green, taste=sweet), Fruit(name=pear, color=green, taste=sweet)]
""".trimMargin()
)
}

"support type inference for subtypes of collection" {
val tests = listOf(
TestSealed.Test1("test1"),
Expand All @@ -60,3 +103,9 @@ class ShouldContainTest : WordSpec({
}
}
})

private object FruitEquality: Equality<Fruit> {
override fun name() = "fruit equality"
override fun verify(actual: Fruit, expected: Fruit): EqualityResult =
SimpleEqualityResult(actual == expected, SimpleEqualityResultDetail { "Some Mesasge" })
}

0 comments on commit aa0813b

Please sign in to comment.