Debería ser
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
El principal matcher o aserción en Kotest es shouldBe. Este matcher se utiliza para verificar la igualdad entre un valor real y uno esperado. La sintaxis sigue el formato actual shouldBe expected. Por ejemplo:
val a = "samuel"
val b = a.take(3)
b shouldBe "sam"
Cuando dos valores no son iguales, Kotest mostrará un mensaje de error detallado que incluye un enlace de intellij <click to see difference> entre ambos valores. Por ejemplo:
Expected :world
Actual :hello
<Click to see difference>
Nota: puedes verificar que dos valores no son iguales usando shouldNotBe.
val a = "samuel"
val b = a.take(3)
b shouldNotBe "bob"
El matcher shouldBe puede combinarse con power assert para obtener mayor efectividad.
Internamente, Kotest utiliza el método equals pero añade lógica adicional para determinar igualdad en tipos donde la comparación simple de objetos no es adecuada. Por ejemplo, en JVM es conocido que Arrays con el mismo contenido no se considerarán iguales al usar el método equals. Otro ejemplo son primitivos de diferentes tipos aunque tengan el mismo valor.
Esta lógica está encapsulada en la typeclass Eq que Kotest usa internamente. También es posible definir tu propia lógica de igualdad para tipos implementando Eq y registrándola en Kotest.
Instancias personalizadas de Eq
Veamos un ejemplo de creación de una instancia personalizada de Eq para comparar objetos Foo. Primero, la definición de Foo:
data class Foo(val value: String)
Luego implementamos la typeclass Eq con la lógica de igualdad deseada, devolviendo un EqResult que puede ser Success o Failure:
Aquí estamos diciendo que si un Foo contiene el string hello y el otro contiene world, entonces son iguales. Para devolver un mensaje de error usamos AssertionErrorBuilder, un ayudante para construir el AssertionError concreto apropiado según la plataforma de ejecución.
object FooEq : Eq<Foo> {
override fun equals(actual: Foo, expected: Foo, context: EqContext): EqResult {
return if (actual.value == "hello" && expected.value == "world")
EqResult.Success
else EqResult.Failure {
AssertionErrorBuilder.create().withMessage("I don't like foo").build()
}
}
}
Si especificamos los valores esperado y real al constructor de errores, el enlace <click to see difference> se generará automáticamente.
Then we register it with Kotest, specifying the type that we want to use it for. Here we are
using project config to set it up before any tests are run. We could do this at the
spec level too, but bear in mind that this registration is global, so if you are running tests in parallel a per-spec
registration will be non-deterministic. See per-call overrides with withEqs below
for a parallel-safe alternative scoped to a single comparison.
class ProjectConfig : AbstractProjectConfig() {
override suspend fun beforeProject() {
DefaultEqResolver.register(Foo::class, FooEq)
}
}
Finalmente, podemos usar nuestra instancia personalizada de Eq en las pruebas simplemente usando shouldBe o shouldNotBe normalmente.
test("custom eq should be selected if both sides are the same type") {
Foo("hello") shouldBe Foo("world")
}
Las instancias personalizadas de Eq solo se seleccionan si ambos lados de la llamada son exactamente del tipo especificado al registrarse. Los subtipos no se seleccionan automáticamente y deben registrarse por separado.
Per-call overrides with withEqs
The global DefaultEqResolver.register(...) API mutates a shared map, so it cannot safely express
"this BigDecimal comparison should ignore scale, but the next one should not" — especially when
specs run in parallel. withEqs provides per-call Eq overrides that are scoped to a single
comparison and never touch the global resolver.
val a = BigDecimal("3.14")
val b = BigDecimal("3.140")
a shouldNotBe b // default Eq compares with scale
a withEqs {
register<BigDecimal>(BigDecimalIgnoreScaleEq)
} shouldBe b // ignores scale only for this line
a shouldNotBe b // back to default
Overrides propagate through nested collection, map and data-class comparisons because they ride on
the EqContext that's threaded through every recursive Eq call:
val rates = mapOf("USD" to BigDecimal("3.14"), "EUR" to BigDecimal("2.99"))
val expected = mapOf("USD" to BigDecimal("3.140"), "EUR" to BigDecimal("2.990"))
rates withEqs {
register<BigDecimal>(BigDecimalIgnoreScaleEq)
} shouldBe expected
An override only fires when the runtime types of actual and expected match, mirroring the
type-match guard used by the global resolver.
withEqs also composes with shouldNotBe for negative comparisons under the same overrides:
val a = BigDecimal("1.00")
val b = BigDecimal("2")
a withEqs {
register<BigDecimal>(BigDecimalIgnoreScaleEq)
} shouldNotBe b