Skip to main content

Matchers comunes en RSpec

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/

RSpec es uno de los frameworks de pruebas más populares en el ecosistema Ruby, y una de sus fortalezas más destacadas es la expresividad de sus matchers: construcciones que permiten definir con claridad y precisión lo que esperamos que ocurra en el código bajo prueba.

En esta lección, revisaremos los matchers más comunes que ofrece RSpec, cómo se utilizan para construir expectativas legibles y directas, y qué diferencias existen con otros frameworks como Kotest en Kotlin. También discutiremos sus ventajas y limitaciones desde la perspectiva del diseño de bibliotecas y herramientas reutilizables.

Ejemplos Comunes de Matchers en RSpec

Igualdad y Desigualdad con eq

El matcher eq en RSpec verifica si dos valores son iguales estructuralmente. Esto significa que compara los contenidos mediante el método ==, no su identidad de objeto (equal?). Por lo tanto, dos objetos distintos pueden ser considerados iguales si su contenido lo es.

💡 Ejemplo contextualizado

Supongamos que estás desarrollando una biblioteca para manejar configuraciones de usuario. Una de las funciones devuelve un objeto con la configuración por defecto. Queremos asegurarnos de que esa función construya el contenido correctamente, sin preocuparnos por si es la misma instancia:

expect(default_config).to eq(UserConfig.new)
¿Qué acabamos de hacer?

Esta prueba verifica que default_config devuelva una instancia con los valores por defecto esperados. Aunque se trata de dos objetos distintos, la prueba pasa porque eq utiliza el método ==, y en este caso hemos definido que dos configuraciones son iguales si tienen el mismo theme y notifications_enabled.

Esto es especialmente útil al diseñar bibliotecas, ya que permite verificar el contenido esperado sin acoplarse a la identidad exacta de la instancia.

❌ Desigualdad

Si lo que deseas es comprobar que dos valores no son iguales estructuralmente, puedes usar el matcher not_to (o to_not):

expect(test_config).not_to eq(default_config)

Esto asegura que, aunque ambos objetos sean del mismo tipo, sus contenidos no son equivalentes según la definición de ==.

¿Qué acabamos de hacer?
  • expect(x).to eq(y) → verifica que x == y
  • expect(x).not_to eq(y) → verifica que x != y

Ambas formas respetan el método == definido en la clase. Esto significa que puedes adaptar qué se considera "igual" para cada tipo de objeto según tus necesidades.

Igualdad referencial

Si necesitas comprobar que dos objetos son exactamente la misma instancia (es decir, ocupan el mismo lugar en memoria), puedes usar:

expect(a).to equal(b)

Este matcher no usa ==, sino equal?, que compara identidad.

Nulidad

En RSpec, puedes verificar si un valor es nil (nulo) usando los matchers be_nil y not_to be_nil.

Esto es especialmente útil al construir bibliotecas que retornan valores opcionales o que permiten estados vacíos como parte de su diseño. Asegurar la presencia o ausencia de un valor es una parte fundamental del control de flujo y la validación de resultados.

💡 Ejemplo contextualizado

Supongamos que estás desarrollando una biblioteca que consulta configuraciones opcionales de usuario. Si no existe una configuración específica, tu función debe retornar nil:

expect(get_config("color_theme")).to be_nil
¿Qué acabamos de hacer?

Este test verifica que la función get_config retorna nil cuando se consulta una clave que no ha sido definida. Esto es útil para APIs que permiten valores opcionales, y nos ayuda a validar su comportamiento en escenarios comunes como configuraciones faltantes, respuestas vacías, o estados iniciales.

✅ Para verificar que un valor no sea nulo

También puedes validar explícitamente que un resultado no sea nil usando not_to be_nil:

expect(user.name).not_to be_nil

Esto es útil en validaciones donde se espera que el resultado esté siempre presente, por ejemplo, después de una carga exitosa o una conversión válida.

¿Qué acabamos de hacer?
  • be_nil verifica si un valor es exactamente nil.
  • not_to be_nil comprueba que existe algún valor distinto de nil—sin importar cuál.

Contenido en cadenas

RSpec ofrece varios matchers para trabajar con cadenas de texto, permitiendo verificar desde contenido parcial hasta coincidencias más específicas como prefijos o sufijos. Esto es muy útil en bibliotecas que generan mensajes, encabezados, logs o formatos estructurados.

🔍 Matchers disponibles

  • include(substring)
    Verifica que la cadena contenga una subcadena específica.

  • start_with(prefix)
    Verifica que la cadena comience con un prefijo determinado.

  • end_with(suffix)
    Verifica que la cadena termine con un sufijo específico.

💡 Ejemplo contextualizado

Supongamos que estás desarrollando una biblioteca que genera mensajes de log. Cada mensaje debe comenzar con un nivel de severidad ([INFO], [WARN], etc.), incluir un texto principal, y terminar con un identificador de evento:

expect(log).to start_with("[WARN]")
expect(log).to include("Bloody New Year's Eve")
expect(log).to end_with("#friend-001")
¿Qué acabamos de hacer?

Este test valida que un mensaje de log generado por la biblioteca incluya tres elementos fundamentales:

  • Un nivel de alerta ([WARN]), necesario para categorizar la gravedad del evento.
  • Una descripción textual del evento ("Bloody New Year's Eve").
  • Un identificador de evento (#friend-001).

Este tipo de test sería útil en una biblioteca que formatea registros de auditoría o trazabilidad de eventos críticos en sistemas complejos.

🧬 Matchers de tipo y clase

En RSpec, puedes verificar si un objeto es de cierto tipo o clase usando los matchers be_a, be_an, be_instance_of y be_kind_of. Esto es especialmente útil cuando estás desarrollando una biblioteca y quieres asegurarte de que tus funciones devuelvan objetos del tipo correcto o compatible.

be_a / be_an

Verifican si un objeto es una instancia de una clase o de una subclase.

expect(user).to be_a(User)
expect(token).to be_an(String)

be_instance_of

Verifica si un objeto es exactamente de una clase, sin permitir subclases.

expect(user).to be_instance_of(User) # Falla si `user` es de una subclase como `AdminUser`

be_kind_of

Alias de be_a / be_an, útil cuando se trabaja con jerarquías de clases o módulos incluidos.

📌 Ejemplo

Supongamos que estás desarrollando una biblioteca para procesar comandos CLI. Cada comando debe devolver un resultado que implementa una interfaz común (CommandResult), pero también puedes tener subtipos para representar distintos casos.

result = CommandProcessor.run("version")

expect(result).to be_a(CommandResult)
expect(result).to be_instance_of(SuccessResult)
¿Qué acabamos de hacer?

Este test verifica dos cosas:

  • Que run("version") devuelve un objeto compatible con CommandResult (be_a) — útil si otras clases heredan de él.
  • Que el tipo exacto del resultado sea SuccessResult (be_instance_of) — útil para distinguir variantes específicas de resultado.

Este enfoque es común en bibliotecas que definen jerarquías de tipos para representar distintos estados o resultados, y permite mantener un diseño extensible sin sacrificar la precisión de las pruebas.