The Scala Toolkit

How to write asynchronous tests?

Language

You can require the entire toolkit in a single line:

//> using toolkit latest

MUnit, being a testing framework, is only available in test files: files in a test directory or ones that have the .test.scala extension. Refer to the Scala CLI documentation to learn more about the test scope.

Alternatively, you can require just a specific version of MUnit:

//> using dep org.scalameta::munit:1.0.0-M7

In your build.sbt file, you can add the dependency on toolkit-test:

lazy val example = project.in(file("."))
  .settings(
    scalaVersion := "3.3.3",
    libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
  )

Here the Test configuration means that the dependency is only used by the source files in src/test.

Alternatively, you can require just a specific version of MUnit:

libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M7" % Test

In your build.sc file, you can add a test object extending Tests and TestModule.Munit:

object example extends ScalaModule {
  def scalaVersion = "3.3.3"
  object test extends Tests with TestModule.Munit {
    def ivyDeps =
      Agg(
        ivy"org.scala-lang::toolkit-test:0.1.7"
      )
  }
}

Alternatively, you can require just a specific version of MUnit:

ivy"org.scalameta::munit:1.0.0-M7"

Asynchronous tests

In Scala, it’s common for an asynchronous method to return a Future. MUnit offers special support for Futures.

For example, consider an asynchronous variant of a square method:

import scala.concurrent.{ExecutionContext, Future}

object AsyncMathLib {
  def square(x: Int)(implicit ec: ExecutionContext): Future[Int] =
    Future(x * x)
}
import scala.concurrent.{ExecutionContext, Future}

object AsyncMathLib:
  def square(x: Int)(using ExecutionContext): Future[Int] =
    Future(x * x)

A test itself can return a Future[Unit]. MUnit will wait behind the scenes for the resulting Future to complete, failing the test if any assertion fails.

You can therefore write the test as follows:

// Import the global execution context, required to call async methods
import scala.concurrent.ExecutionContext.Implicits.global

class AsyncMathLibTests extends munit.FunSuite {
  test("square") {
    for {
      squareOf3 <- AsyncMathLib.square(3)
      squareOfMinus4 <- AsyncMathLib.square(-4)
    } yield {
      assertEquals(squareOf3, 9)
      assertEquals(squareOfMinus4, 16)
    }
  }
}
// Import the global execution context, required to call async methods
import scala.concurrent.ExecutionContext.Implicits.global

class AsyncMathLibTests extends munit.FunSuite:
  test("square") {
    for
      squareOf3 <- AsyncMathLib.square(3)
      squareOfMinus4 <- AsyncMathLib.square(-4)
    yield
      assertEquals(squareOf3, 9)
      assertEquals(squareOfMinus4, 16)
  }

The test first asynchronously computes square(3) and square(-4). Once both computations are completed, and if they are both successful, it proceeds with the calls to assertEquals. If any of the assertion fails, the resulting Future[Unit] fails, and MUnit will cause the test to fail.

You may read more about asynchronous tests in the MUnit documentation. It shows how to use other asynchronous types besides Future.

Contributors to this page: