El ecosistema de testing en Scala tiene más opciones que muchos otros lenguajes, lo cual puede ser abrumador al principio. Las tres más relevantes en 2026 son ScalaTest, MUnit y ZIO Test. Cada una tiene un enfoque distinto y encaja mejor en contextos diferentes. Aquí va una guía práctica para elegir sin complicaciones.
ScalaTest: la más popular y flexible
ScalaTest es la librería de testing más usada en proyectos Scala. Tiene muchos estilos de especificación: FunSpec, FlatSpec, WordSpec, AnyFunSuite... La proliferación de estilos es a la vez su punto fuerte y su punto débil. Por eso, en equipos nuevos conviene ponerse de acuerdo en uno y no mezclar.
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class CalculadoraSpec extends AnyFlatSpec with Matchers:
"suma" should "devolver la suma de dos números" in {
val resultado = 2 + 3
resultado should be(5)
}
it should "manejar números negativos" in {
(-1 + -2) should equal(-3)
}
"una lista" should "contener el elemento esperado" in {
List(1, 2, 3) should contain(2)
}
Los matchers de ScalaTest son su gran virtud: should be, should equal, should contain, should have length... La API es muy expresiva y los mensajes de error cuando falla un test son bastante claros. Para tests de integración que necesitan setup y teardown, BeforeAndAfter y BeforeAndAfterAll son los traits a extender.
MUnit: ligera y directa
MUnit, de Scalameta, es la alternativa minimalista. Un fichero con tests en MUnit es simplemente una clase que extiende munit.FunSuite:
class MiSuite extends munit.FunSuite:
test("suma básica") {
assertEquals(2 + 3, 5)
}
test("conversión de string") {
val resultado = "42".toIntOption
assertEquals(resultado, Some(42))
}
test("fallo esperado") {
intercept[NumberFormatException] {
"no_es_número".toInt
}
}
Lo que hace a MUnit especialmente atractiva es su integración con efectos. Para tests con Cats Effect, existe munit-cats-effect que permite devolver IO[Unit] directamente desde los tests sin ningún boilerplate:
import cats.effect.*
import munit.CatsEffectSuite
class MiSuiteIO extends CatsEffectSuite:
test("efecto que produce un valor") {
val efecto: IO[Int] = IO.pure(42)
assertIO(efecto, 42)
}
test("efecto con transformación") {
val efecto = IO.pure("hola").map(_.toUpperCase)
assertIO(efecto, "HOLA")
}
ZIO Test: tests como valores ZIO
ZIO Test está integrado en el ecosistema ZIO y usa el mismo modelo de efectos. Los tests son valores ZIO, lo que significa que puedes usar cualquier combinador de ZIO en tus tests: ZIO.foreach, .race, manejo de errores, etc.
import zio.*
import zio.test.*
object ServicioSpec extends ZIOSpecDefault:
def spec = suite("Servicio de usuarios")(
test("crea un usuario correctamente") {
for
usuario <- ZIO.succeed(crearUsuario("Ana", 25))
yield assertTrue(usuario.nombre == "Ana") && assertTrue(usuario.edad == 25)
},
test("rechaza edad negativa") {
val resultado = ZIO.attempt(crearUsuario("Bob", -1))
assertZIO(resultado.exit)(fails(isSubtype[IllegalArgumentException](anything)))
},
suite("con entorno real")(
test("accede al reloj del sistema") {
for
ahora <- Clock.currentTime(java.util.concurrent.TimeUnit.MILLISECONDS)
yield assertTrue(ahora > 0)
} @@ TestAspect.live
)
)
case class Usuario(nombre: String, edad: Int)
def crearUsuario(nombre: String, edad: Int): Usuario =
if edad < 0 then throw IllegalArgumentException("Edad inválida")
else Usuario(nombre, edad)
ZIO Test proporciona implementaciones de prueba para todos los servicios del entorno (Clock, Console, Random, System), lo que facilita testear código que depende de la hora, de entradas de usuario o de variables de entorno sin tocar el sistema real.
Property-based testing con ScalaCheck
Ningún artículo de testing en Scala estaría completo sin mencionar ScalaCheck, que implementa property-based testing al estilo de QuickCheck (Haskell). En lugar de escribir casos de prueba concretos, defines propiedades que deben cumplirse para cualquier entrada:
import org.scalacheck.Prop.forAll
import org.scalacheck.Properties
object ListaProps extends Properties("Lista"):
property("reverse") = forAll { (lista: List[Int]) =>
lista.reverse.reverse == lista
}
property("longitud tras añadir") = forAll { (lista: List[Int], elem: Int) =>
(lista :+ elem).length == lista.length + 1
}
ScalaCheck genera cientos de valores aleatorios para verificar la propiedad, y si encuentra un caso que la viola, lo minimiza hasta dar el ejemplo más pequeño posible que falla. Es una forma de encontrar bugs que los tests manuales raramente descubren.
Cuándo usar cada librería
La guía corta: MUnit para proyectos con Cats Effect o simplemente si quieres algo ligero. ZIO Test para proyectos ZIO (la integración es perfecta). ScalaTest cuando el equipo ya lo conoce o cuando necesitas la flexibilidad de sus múltiples estilos. ScalaCheck como complemento de cualquiera de las anteriores para lógica pura con propiedades bien definidas.
Las tres funcionan con SBT y con Scala CLI. Para correr los tests:
// Con SBT // $ sbt test // $ sbt "testOnly com.ejemplo.MiSpec" // Con Scala CLI // $ scala-cli test .
El testing en Scala se beneficia mucho del sistema de tipos: cuando modelas errores como valores (con Either, ZIO o IO), los tests son más fáciles de escribir porque no necesitas capturar excepciones inesperadas. Los artículos de ZIO 2 y Cats Effect 3 de esta misma serie explican en más detalle ese modelo.
Imagen: Pexels / Daniil Komov
