Skip to content
AgileLogics
5 min read

Testing with MUnit

Prerequisites: Building with sbt and Scala CLI

MUnit is a simple, fast testing library, and the default in Scala CLI and the Scala 3 project template. (ScalaTest is the other widely used option, with several testing styles.)

// illustrative
// Pricing.scala
object Pricing:
  def discount(total: BigDecimal): BigDecimal =
    require(total >= 0, "total cannot be negative")
    if total >= 100 then total * BigDecimal("0.1") else BigDecimal(0)
// illustrative
// Pricing.test.scala
//> using test.dep org.scalameta::munit:1.1.0

class PricingSuite extends munit.FunSuite:
  test("no discount under 100") {
    assertEquals(Pricing.discount(50), BigDecimal(0))
  }

  test("10% discount at 100 or more") {
    assertEquals(Pricing.discount(200), BigDecimal(20))
  }

  test("negative totals are rejected") {
    intercept[IllegalArgumentException](Pricing.discount(-1))
  }
$ scala test .
PricingSuite:
  + no discount under 100 0.012s
  + 10% discount at 100 or more 0.001s
  + negative totals are rejected 0.002s
  • A test suite is a class extending munit.FunSuite; each test is one case.
  • assertEquals(obtained, expected) shows a clear diff on failure; assert(condition), intercept[E], and assertNoDiff (for multi-line strings) cover most other needs.
  • In an sbt project, tests live in src/test/scala and run with sbt test.

Fixtures and property-based testing

// illustrative
class ListSuite extends munit.ScalaCheckSuite:
  import org.scalacheck.Prop.forAll

  property("reversing twice gives the original list") {
    forAll { (xs: List[Int]) => xs.reverse.reverse == xs }
  }

Property-based tests (with ScalaCheck) generate hundreds of random inputs and check that a property always holds — excellent at finding edge cases you didn't think of. Pure functions over immutable data are, happily, the easiest code to test this way.