Comparación por campos
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Cuando queramos comparar solo algunos campos, excluyendo otros de la comparación, debemos usar shouldBeEqualUsingFields:
val expected = Thing(name = "apple", createdAt = Instant.now())
val actual = Thing(name = "apple", createdAt = Instant.now().plusMillis(42L))
actual shouldBeEqualUsingFields {
excludedProperties = setOf(Thing::createdAt)
expected
}
Del mismo modo, podemos indicar explícitamente qué campos comparar, y todos los demás campos quedarán excluidos:
val expected = Thing(name = "apple", createdAt = Instant.now())
val actual = Thing(name = "apple", createdAt = Instant.now().plusMillis(42L))
actual shouldBeEqualUsingFields {
includedProperties = setOf(Thing::name)
expected
}
Para clases anidadas, la comparación se realiza de forma recursiva, como se muestra a continuación:
val doctor1 = Doctor("billy", 23, emptyList())
val doctor2 = Doctor("barry", 23, emptyList())
val city = City("test1", Hospital("test-hospital1", doctor1))
val city2 = City("test2", Hospital("test-hospital2", doctor2))
shouldThrowAny {
city.shouldBeEqualUsingFields {
city2
}
}.message shouldContain """Using fields:
- mainHospital.mainDoctor.age
- mainHospital.mainDoctor.name
- mainHospital.name
- name
Fields that differ:
- mainHospital.mainDoctor.name => expected:<"barry"> but was:<"billy">
- mainHospital.name => expected:<"test-hospital2"> but was:<"test-hospital1">
- name => expected:<"test2"> but was:<"test1">"""
Pero podemos detener explícitamente la comparación recursiva. En el siguiente ejemplo, estamos comparando instancias de la clase Doctor como un todo, sin comparar sus campos individuales. Por lo tanto, se detecta la diferencia en mainHospital.mainDoctor, a diferencia de las diferencias detectadas en mainHospital.mainDoctor.name en el ejemplo anterior:
val doctor1 = Doctor("billy", 22, emptyList())
val doctor2 = Doctor("billy", 22, emptyList())
val city = City("test", Hospital("test-hospital", doctor1))
val city2 = City("test", Hospital("test-hospital", doctor2))
shouldFail {
city.shouldBeEqualUsingFields {
useDefaultShouldBeForFields = listOf(Doctor::class)
city2
}
}.message shouldContain """Using fields:
- mainHospital.mainDoctor
- mainHospital.name
- name
Fields that differ:
- mainHospital.mainDoctor =>
También podemos proporcionar matchers personalizados para campos. En el siguiente ejemplo, estamos comparando SimpleDataClass::name como cadenas sin distinguir entre mayúsculas y minúsculas:
val expected = SimpleDataClass("apple", 1.0, LocalDateTime.now())
val actual = expected.copy(name = "Apple")
shouldThrow<AssertionError> {
actual shouldBeEqualUsingFields expected
}.message.shouldContainInOrder(
"Fields that differ:",
"""- name => expected:<"apple"> but was:<"Apple">""",
)
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
SimpleDataClass::name to matchStringsIgnoringCase
)
expected
}
Kotest proporciona los siguientes matchers de reemplazo:
matchBigDecimalsIgnoringScale
val expected = WithManyFields(
BigDecimal.ONE,
LocalDateTime.now(),
ZonedDateTime.now(),
OffsetDateTime.now(),
Instant.now()
)
val actual = expected.copy(bigDecimal = BigDecimal("1.000"))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
WithManyFields::bigDecimal to matchBigDecimalsIgnoringScale()
)
expected
}
matchDoublesWithTolerance
val expected = SimpleDataClass("apple", 1.0, LocalDateTime.now())
val actual = expected.copy(weight = 1.001)
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
SimpleDataClass::weight to matchDoublesWithTolerance(0.01)
)
expected
}
matchInstantsWithTolerance
val expected = WithManyFields(
BigDecimal.ONE,
LocalDateTime.now(),
ZonedDateTime.now(),
OffsetDateTime.now(),
Instant.now()
)
val actual = expected.copy(instant = expected.instant.plusSeconds(1))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
WithManyFields::instant to matchInstantsWithTolerance(2.seconds)
)
expected
}
matchListsIgnoringOrder
val expected = DataClassWithList("name", listOf(1, 2, 3))
val actual = expected.copy(elements = listOf(3, 2, 1))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
DataClassWithList::elements to matchListsIgnoringOrder<Int>()
)
expected
}
matchLocalDateTimesWithTolerance
val expected = WithManyFields(
BigDecimal.ONE,
LocalDateTime.now(),
ZonedDateTime.now(),
OffsetDateTime.now(),
Instant.now()
)
val actual = expected.copy(localDateTime = expected.localDateTime.plusSeconds(1))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
WithManyFields::localDateTime to matchLocalDateTimesWithTolerance(2.seconds)
)
expected
}
matchOffsetDateTimesWithTolerance
val expected = WithManyFields(
BigDecimal.ONE,
LocalDateTime.now(),
ZonedDateTime.now(),
OffsetDateTime.now(),
Instant.now()
)
val actual = expected.copy(offsetDateTime = expected.offsetDateTime.plusSeconds(1))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
WithManyFields::offsetDateTime to matchOffsetDateTimesWithTolerance(2.seconds)
)
expected
}
matchStringsIgnoringCase
val expected = SimpleDataClass("apple", 1.0, LocalDateTime.now())
val actual = expected.copy(name = "Apple")
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
SimpleDataClass::name to matchStringsIgnoringCase
)
expected
}
matchZonedDateTimesWithTolerance
val expected = WithManyFields(
BigDecimal.ONE,
LocalDateTime.now(),
ZonedDateTime.now(),
OffsetDateTime.now(),
Instant.now()
)
val actual = expected.copy(zonedDateTime = expected.zonedDateTime.plusSeconds(1))
actual shouldBeEqualUsingFields {
overrideMatchers = mapOf(
WithManyFields::zonedDateTime to matchOffsetDateTimesWithTolerance(2.seconds)
)
expected
}
Creación de su propio matcher de reemplazo
Implemente la interfaz Assertable:
fun interface Assertable {
fun assert(expected: Any?, actual: Any?): CustomComparisonResult
}
sealed interface CustomComparisonResult {
val comparable: Boolean
data object NotComparable: CustomComparisonResult {
override val comparable = false
}
data object Equal: CustomComparisonResult {
override val comparable = true
}
data class Different(val assertionError: AssertionError): CustomComparisonResult {
override val comparable = true
}
}
Por ejemplo, aquí está la implementación de matchListsIgnoringOrder:
fun<T> matchListsIgnoringOrder() = Assertable { expected: Any?, actual: Any? ->
customComparison<List<T>>(expected, actual) { expected: List<T>, actual: List<T> ->
actual shouldContainExactlyInAnyOrder expected
}
}
Podemos usar cualquiera de las aserciones should*** de Kotest.