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

add-similarity-to-contain (#53) #3939

Open
wants to merge 2 commits 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 @@ -2,6 +2,7 @@ package io.kotest.matchers.maps

import io.kotest.assertions.ErrorCollectionMode
import io.kotest.assertions.errorCollector
import io.kotest.assertions.print.print
import io.kotest.assertions.runWithMode
import io.kotest.matchers.Matcher
import io.kotest.matchers.MatcherResult
Expand Down Expand Up @@ -69,15 +70,45 @@ fun <V> containAnyValues(vararg values: V): Matcher<Map<*, V>> = object : Matche
}

fun <K, V> contain(key: K, v: V): Matcher<Map<K, V>> = object : Matcher<Map<K, V>> {
override fun test(value: Map<K, V>) = MatcherResult(
value[key] == v,
{ "Map should contain mapping $key=$v but was ${buildActualValue(value)}" },
{ "Map should not contain mapping $key=$v but was $value" }
)
override fun test(value: Map<K, V>): MatcherResult {
val passed = value[key] == v
val possibleMatches = if(passed) "" else describePossibleMatches(key, v, value)
return MatcherResult(
passed,
{ "Map should contain mapping $key=$v but was ${buildActualValue(value)}$possibleMatches" },
{ "Map should not contain mapping $key=$v but was $value" }
)
}

private fun buildActualValue(map: Map<K, V>) = map[key]?.let { "$key=$it" } ?: map

private fun describePossibleMatches(key: K, v: V, map: Map<K, V>): String {
val similarKeys = if(key in map) "" else possibleMatchesDescription(map.keys, key)
val similarOrSameMatches = if(v in map.values) sameValueForOtherKeys(v, map) else similarValues(v, map)
val matchesToInclude = listOf(similarKeys, similarOrSameMatches).filter { it.isNotEmpty() }
return if(matchesToInclude.isEmpty()) "" else "\n${matchesToInclude.joinToString("\n")}"
}

private fun sameValueForOtherKeys(v: V, map: Map<K, V>): String = prettyPrintIfNotEmpty(
list = map.entries.filter { it.value == v },
prefix = "\nSame value found for the following entries: "
)

private fun similarValues(v: V, map: Map<K, V>): String = printIfNotEmpty(
string = possibleMatchesDescription(map.values.toSet(), v),
prefix = "\nSimilar values found:\n"
)

}

internal fun printIfNotEmpty(string: String, prefix: String = "", suffix: String = ""): String =
if(string.isEmpty()) "" else
"$prefix$string$suffix"

internal fun<T> prettyPrintIfNotEmpty(list: List<T>, prefix: String = "", suffix: String = ""): String =
if(list.isEmpty()) "" else
"$prefix${list.print().value}$suffix"

fun <K, V> containAll(expected: Map<K, V>): Matcher<Map<K, V>> =
MapContainsMatcher(expected, ignoreExtraKeys = true)

Expand Down
Expand Up @@ -11,7 +11,9 @@ import io.kotest.matchers.maps.*
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNot
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldHaveLength
import io.kotest.matchers.string.shouldStartWith
import java.util.LinkedList

class MapMatchersTest : WordSpec() {
Expand Down Expand Up @@ -73,7 +75,37 @@ class MapMatchersTest : WordSpec() {
}.message.shouldBe("Map should contain mapping 4=e but was {1=a, 2=b}")
shouldThrow<AssertionError> {
map should contain(2, "a")
}.message.shouldBe("Map should contain mapping 2=a but was 2=b")
}.message.shouldStartWith("Map should contain mapping 2=a but was 2=b")
}
"print a similar key when no exact match" {
val message = shouldThrow<AssertionError> {
mapOf(sweetGreenApple to 1, sweetRedApple to 2) should contain(sweetGreenPear, 1)
}.message
message shouldContain """
| expected: Fruit(name=apple, color=green, taste=sweet),
| but was: Fruit(name=pear, color=green, taste=sweet),
| The following fields did not match:
| "name" expected: <"apple">, but was: <"pear">
""".trimMargin()
}
"print entries with same value" {
val message = shouldThrow<AssertionError> {
mapOf(sweetGreenApple to 1, sweetRedApple to 2) should contain(sweetGreenPear, 1)
}.message
message shouldContain """
|Same value found for the following entries: [Fruit(name=apple, color=green, taste=sweet)=1]
""".trimMargin()
}
"print entries with similar values" {
val message = shouldThrow<AssertionError> {
mapOf(1 to sweetGreenApple, 2 to sweetRedApple) should contain(3, sweetGreenPear)
}.message
message shouldContain """
| expected: Fruit(name=apple, color=green, taste=sweet),
| but was: Fruit(name=pear, color=green, taste=sweet),
| The following fields did not match:
| "name" expected: <"apple">, but was: <"pear">
""".trimMargin()
}
}

Expand Down