Matchers compuestos
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/assertions-kt
Puedes ejecutar el siguiente comando para crear el módulo
./gradlew setupComposedMatchersModule
Mientras se crean los archivos necesarios, puedes leer el código para saber qué está pasando.
val moduleName = "composed-matchers"
val moduleDir = rootProject.file(moduleName)
val printError: (String) -> Unit = System.err::println
tasks.register("setupComposedMatchersModule") {
group = "setup"
description =
"Creates the necessary files for the lesson on composed matchers"
doFirst {
val buildName = "build.gradle.kts"
createModuleDirectory()
createBuildFile(buildName)
createFiles(
packageName = "password",
"BeStrongPasswordTest.kt", "BeStrongPassword.kt"
)
createFiles(
packageName = "email",
"BeValidEmail.kt"
)
}
}
// Función para crear el directorio del módulo
// Si el directorio ya existe, imprime un mensaje de error; de lo contrario,
// intenta crearlo
fun createModuleDirectory() = moduleDir.run {
when {
exists() -> printError("Directory already exists: $absolutePath")
mkdirs() -> println("Directory created: $absolutePath")
else -> printError("Failed to create directory: $absolutePath")
}
}
// Función para crear el archivo 'build.gradle.kts'
// Si el archivo ya existe, imprime un mensaje de error; de lo contrario, lo
// crea con un comentario predeterminado
fun createBuildFile(buildName: String) =
moduleDir.resolve(buildName).run {
if (!exists()) {
writeText("// Intentionally left blank")
println("File created: $absolutePath")
} else {
printError("File already exists: $absolutePath")
}
}
// Función para crear archivos en un paquete específico
// Si un archivo ya existe, imprime un mensaje de error; de lo contrario, lo
// crea con un paquete predeterminado
fun createFiles(
packageName: String, vararg files: String
) {
val group = rootProject.group.toString()
files.forEach { fileName ->
moduleDir.resolve(
"src/test/kotlin/${group.replace(".", "/")}/$packageName"
).run {
mkdirs() // Crea los directorios necesarios
resolve(fileName).run {
if (exists()) {
printError("File already exists: $this")
} else {
writeText("package $group.$packageName\n\n")
println("File created: $this")
}
}
}
}
}
Preocúpate de que el plugin composed-matchers
esté aplicado en el archivo build.gradle.kts
de tu proyecto.
./gradlew setupComposedMatchersModule
Preocúpate de que el nuevo módulo esté incluido en el archivo settings.gradle.kts
.
Los matchers se pueden combinar para crear reglas más complejas mediante la composición de otros matchers más simples. Esto permite verificar condiciones complejas de forma declarativa y legible.
Existen dos operaciones lógicas clave para componer matchers:
- Suma lógica (
Matcher.any
oor
): Se satisface si al menos uno de los matchers es verdadero. - Producto lógico (
Matcher.all
oand
): Se satisface solo si todos los matchers son verdaderos.
Ejemplo de composición de matchers
Vamos a implementar un matcher para verificar que una contraseña es "fuerte". En este contexto, una contraseña fuerte debe:
- Contener al menos un número.
- Contener al menos una letra mayúscula.
- Contener al menos una letra minúscula.
- Tener al menos 16 caracteres.
- Código esencial
- Código completo
- and
- all
val beStrongPassword = containADigit() and
contain("[A-Z]".toRegex()) and
contain("[a-z]".toRegex()) and
haveMinLength(16)
val beStrongPassword = Matcher.all(
containADigit(),
contain("[A-Z]".toRegex()),
contain("[a-z]".toRegex()),
haveMinLength(16)
)
- and
- all
package com.github.username.password
import io.kotest.matchers.and
import io.kotest.matchers.string.contain
import io.kotest.matchers.string.containADigit
import io.kotest.matchers.string.haveMinLength
val beStrongPassword = containADigit() and
contain("[A-Z]".toRegex()) and
contain("[a-z]".toRegex()) and
haveMinLength(16)
package com.github.username.password
import io.kotest.matchers.Matcher
import io.kotest.matchers.compose.all
import io.kotest.matchers.string.contain
import io.kotest.matchers.string.containADigit
import io.kotest.matchers.string.haveMinLength
val beStrongPassword = Matcher.all(
containADigit(),
contain("[A-Z]".toRegex()),
contain("[a-z]".toRegex()),
haveMinLength(16)
)
Uso en las pruebas
Una vez que hemos definido el matcher beStrongPassword
, podemos utilizarlo en nuestras pruebas para asegurarnos de que las contraseñas cumplen con los requisitos de fortaleza:
- Código esencial
- Código completo
"Password1AaBbCcDdEeFf" should beStrongPassword
"PasswordAaBbCcDdEeFf" shouldNot beStrongPassword
package com.github.username.password
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldNot
class BeStrongPasswordTest : FreeSpec({
"A password" - {
("when it contains a digit, uppercase and lowercase letters, and at " +
"least 16 characters") - {
"then it is considered strong" {
"Password1AaBbCcDdEeFf" should beStrongPassword
}
}
"when it does not contain a digit" - {
"then it is not considered strong" {
"PasswordAaBbCcDdEeFf" shouldNot beStrongPassword
}
}
"when it does not contain an uppercase letter" - {
"then it is not considered strong" {
"password1aabbccddee" shouldNot beStrongPassword
}
}
"when it does not contain a lowercase letter" - {
"then it is not considered strong" {
"PASSWORD1AABBCCDDEE" shouldNot beStrongPassword
}
}
"when it does not have at least 16 characters" - {
"then it is not considered strong" {
"Password1AaBbCc" shouldNot beStrongPassword
}
}
}
})
La composición de matchers es una herramienta poderosa que permite crear validaciones complejas de manera concisa y reutilizable.
Ejercicio: Validación de Correo Electrónico
Crea un matcher compuesto que valide si una cadena es un correo electrónico válido. Un correo válido debe:
- Contener un símbolo
@
. - Tener al menos un carácter antes del
@
. - Tener al menos un carácter después del
@
y antes del.
. - Terminar con un dominio que tenga al menos 2 caracteres después del
.
.
Ver hints
- Puedes utilizar
indexOf: String.(String) -> Int
para encontrar la posición de un carácter en una cadena. - Puedes utilizar
substringAfterLast: String.(String) -> String
para obtener la parte de una cadena después de la última ocurrencia de un carácter. - Algunos matchers útiles son
contain
yhaveLengthAtLeast
.
Solución
fun beValidEmail(): Matcher<String> {
val containsAt = contain("@")
val hasTextBeforeAt = Matcher<String> { value ->
MatcherResult(
value.indexOf("@") > 0,
{ "The email should have text before '@'" },
{ "The email has valid text before '@'" }
)
}
val hasTextAfterAt = Matcher<String> { value ->
MatcherResult(
value.indexOf("@") < value.lastIndexOf(".") - 1,
{ "The email should have text between '@' and '.'" },
{ "The email has valid text between '@' and '.'" }
)
}
val validDomain = Matcher<String> { value ->
MatcherResult(
value.substringAfterLast(".").length >= 2,
{ "The domain should have at least 2 characters" },
{ "The domain is valid" }
)
}
return containsAt and hasTextBeforeAt and hasTextAfterAt and validDomain
}
¿Qué aprendimos?
En esta sección exploramos cómo utilizar matchers compuestos para validar condiciones complejas de forma modular y reutilizable.
Puntos clave
- Los matchers se pueden combinar usando operaciones lógicas como suma lógica (
or
/Matcher.any
) y producto lógico (and
/Matcher.all
) para crear validaciones más sofisticadas. - Al combinar matchers simples, logramos verificar condiciones complejas, como la fortaleza de una contraseña o la validez de un correo electrónico.
- La composición de matchers es útil para mantener nuestras pruebas limpias y legibles, a la vez que facilita la reutilización del código.
Al final, hemos visto cómo estos conceptos se aplican a escenarios prácticos y cómo los matchers permiten crear reglas concisas para validar datos en nuestras pruebas.
Bibliografías Recomendadas
- 🌐 "Composed Matchers | Kotest." Accedido: 22 de septiembre de 2024. [En línea]. Disponible en: https://kotest.io/docs/assertions/composed-matchers.html