Ir al contenido principal
Versión: 5.8.x

Matchers Compuestos

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Los matchers compuestos pueden crearse para cualquier tipo combinando uno o más matchers. Esto permite construir matchers complejos a partir de otros más simples. Existen dos operaciones lógicas para componer matchers: suma lógica (Matcher.any) y producto lógico (Matcher.all).

Imaginemos que queremos definir un Matcher para contraseñas que verifique que containADigit(), contain(Regex("[a-z]")) y contain(Regex("[A-Z]")). Podemos componer estos matchers así:

val passwordMatcher = Matcher.all(
containADigit(), contain(Regex("[a-z]")), contain(Regex("[A-Z]"))
)

Podemos añadir entonces una función de extensión:

fun String.shouldBeStrongPassword() = this shouldBe passwordMatcher

De modo que pueda invocarse así:

"StrongPassword123".shouldBeStrongPassword()
"WeakPassword".shouldBeStrongPassword() // would fail

Por analogía, podemos construir un matcher compuesto usando Matcher.any. En este caso, passwordMatcher fallará solo si todos los matchers fallan; en caso contrario, pasará la validación.

val passwordMatcher = Matcher.any(
containADigit(), contain(Regex("[a-z]")), contain(Regex("[A-Z]"))
)

También pueden crearse matchers compuestos para cualquier class o interface combinando otros matchers junto con la propiedad a extraer para realizar las comprobaciones.

Por ejemplo, supongamos que tenemos las siguientes estructuras:

data class Person(
val name: String,
val age: Int,
val address: Address,
)

data class Address(
val city: String,
val street: String,
val buildingNumber: String,
)

Y nuestro objetivo es tener un matcher Person que verifique personas en Varsovia. Podemos definir matchers para cada uno de esos componentes así:

fun nameMatcher(name: String) = Matcher<String> {
MatcherResult(
value == name,
{ "Name $value should be $name" },
{ "Name $value should not be $name" }
)
}

fun ageMatcher(age: Int) = Matcher<Int> {
MatcherResult(
value == age,
{ "Age $value should be $age" },
{ "Age $value should not be $age" }
)
}

val addressMatcher = Matcher<Address> {
MatcherResult(
value == Address("Warsaw", "Test", "1/1"),
{ "Address $value should be Test 1/1 Warsaw" },
{ "Address $value should not be Test 1/1 Warsaw" }
)
}

Ahora podemos combinar estos elementos para crear un matcher de Juan en Varsovia. Observa que especificamos la propiedad a extraer para pasar a cada matcher sucesivamente.

fun personMatcher(name: String, age: Int) = Matcher.all(
havingProperty(nameMatcher(name) to Person::name),
havingProperty(ageMatcher(age) to Person::age),
havingProperty(addressMatcher to Person::address)
)

Y también podemos añadir la variante de extensión:

fun Person.shouldBePerson(name: String, age: Int) = this shouldBe personMatcher(name, age)

Luego lo invocamos de esta manera:

Person("John", 21, Address("Warsaw", "Test", "1/1")).shouldBePerson("John", 21)
Person("Sam", 22, Address("Chicago", "Test", "1/1")).shouldBePerson("John", 21) // would fail