The Scala Toolkit

How to manage the resources of a test?

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"

FunFixture

In MUnit, we use functional fixtures to manage resources in a concise and safe way. A FunFixture creates one resource for each test, ensuring that each test runs in isolation from the others.

In a test suite, you can define and use a FunFixture as follows:

class FileTests extends munit.FunSuite {
  val usingTempFile: FunFixture[os.Path] = FunFixture(
    setup = _ => os.temp(prefix = "file-tests"),
    teardown = tempFile => os.remove(tempFile)
  )
  usingTempFile.test("overwrite on file") { tempFile =>
    os.write.over(tempFile, "Hello, World!")
    val obtained = os.read(tempFile)
    assertEquals(obtained, "Hello, World!")
  }
}
class FileTests extends munit.FunSuite:
  val usingTempFile: FunFixture[os.Path] = FunFixture(
    setup = _ => os.temp(prefix = "file-tests"),
    teardown = tempFile => os.remove(tempFile)
  )
  usingTempFile.test("overwrite on file") { tempFile =>
    os.write.over(tempFile, "Hello, World!")
    val obtained = os.read(tempFile)
    assertEquals(obtained, "Hello, World!")
  }

usingTempFile is a fixture of type FunFixture[os.Path]. It contains two functions:

  • The setup function, of type TestOptions => os.Path, creates a new temporary file.
  • The teardown function, of type os.Path => Unit, deletes this temporary file.

We use the usingTempFile fixture to define a test that needs a temporary file. Notice that the body of the test takes a tempFile, of type os.Path, as parameter. The fixture automatically creates this temporary file, calls its setup function, and cleans it up after the test by calling teardown.

In the example, we used a fixture to manage a temporary file. In general, fixtures can manage other kinds of resources, such as a temporary folder, a temporary table in a database, a connection to a local server, and so on.

Composing FunFixtures

In some tests, you may need more than one resource. You can use FunFixture.map2 to compose two functional fixtures into one.

val using2TempFiles: FunFixture[(os.Path, os.Path)] =
  FunFixture.map2(usingTempFile, usingTempFile)

using2TempFiles.test("merge two files") {
  (file1, file2) =>
    // body of the test
}
val using2TempFiles: FunFixture[(os.Path, os.Path)] =
  FunFixture.map2(usingTempFile, usingTempFile)

using2TempFiles.test("merge two files") {
  (file1, file2) =>
    // body of the test
}

Using FunFixture.map2 on a FunFixture[A] and a FunFixture[B] returns a FunFixture[(A, B)].

Other fixtures

FunFixture is the recommended type of fixture because:

  • it is explicit: each test declares the resource they need,
  • it is safe to use: each test uses its own resource in isolation.

For more flexibility, MUnit contains other types of fixtures: the reusable fixture, the ad-hoc fixture and the asynchronous fixture. Learn more about them in the MUnit documentation.

Contributors to this page: