Skip to main content

Test

The test command runs test suites in the test sources. Test sources are compiled separately (after the 'main' sources), and may use different dependencies, compiler options, and other configurations.

By default, all command line options apply to both the main and test sources, so using directives can be used to provide test-specific configurations.

Test sources

A source file is treated as test source if:

  • the file name ends with .test.scala, or
  • the file comes from a directory that is provided as input, and the relative path from that file to its original directory contains a test directory, or
  • it contains the //> using target.scope test directive
caution

The using target directives are an experimental feature, and may change in future versions of Scala CLI.

The second rule may sound a bit complicated, so let's explain it using following directory structure:

tree example
example
├── a.scala
├── a.test.scala
└── src
├── main
│ └── scala
│ └── d.scala
├── test
│ └── scala
│ └── b.scala
└── test_unit
└── scala
└── e.scala

Given that directory structure, let's analyze what file(s) will be treated as tests based on the provided inputs.

scala-cli example results in the following files being treated as test sources:

  • a.test.scala, since it ends with .test.scala
  • src/test/scala/b.scala, since the path to that directory contains a directory named test

Note that e.scala is not treated as a test source since it lacks a parent directory in its relative path that is exactly named test (the nametest_unit starts with test, but Scala CLI only looks for parent directories on the relative path with the exact name test).

scala-cli example/src results in src/test/scala/b.scala being treated as a test file since its relative path (test/scala/b.scala) contains a directory named test.

Conversely, scala-cli example/src/test results in no test sources, since the relative path to b.scala does not contain test (the fact that the directory provided as input is named test does not make its content a test source).

Directives take precedence over file or path names, so using target main can be used to force test/a.scala or a.test.scala to not be treated as tests.

As a rule of thumb, we recommend naming all of your test files with the .test.scala suffix.

Test directives

When configuring your tests with using directives, it's usually advised to use their test scope equivalents, so that only tests are affected.

For example, when declaring a test framework dependency, in most cases you wouldn't need it when running your whole app, you only need it in tests. So rather than declare it globally with using dep, you can use the test.dep directive:

//> using test.dep org.scalameta::munit::0.7.29

For more details on test directives, see the using directives guide.

Test framework

In order to run tests with a test framework, add the framework dependency to your application. Some of the most popular test frameworks in Scala are:

  • munit: org.scalameta::munit::0.7.29
  • utest: com.lihaoyi::utest::0.8.2
  • ScalaTest: org.scalatest::scalatest::3.2.17
  • JUnit 4, which can be used via a dedicated interface: com.github.sbt:junit-interface:0.13.3
  • Weaver: com.disneystreaming::weaver-cats:0.8.3. You may need to specify weaver's test framework with //> using testFramework "weaver.framework.CatsEffect" if you had other test framework in your dependencies.

The following example shows how to run an munit-based test suite:

//> using test.dep org.scalameta::munit::0.7.29

class MyTests extends munit.FunSuite {
test("foo") {
assert(2 + 2 == 4)
}
}
scala-cli test MyTests.test.scala
Compiling project (1 Scala source)
Compiled project
MyTests:
+ foo 0.143s

Filter test suite

Passing the --test-only option to the test sub-command filters the test suites to be run:

//> using test.dep org.scalameta::munit::0.7.29
package tests.only

class BarTests extends munit.FunSuite {
test("bar") {
assert(2 + 3 == 5)
}
}
package tests

class HelloTests extends munit.FunSuite {
test("hello") {
assert(2 + 2 == 4)
}
}
scala-cli test . --test-only 'tests.only*' 
# tests.only.BarTests:
# + bar 0.045s

Filter test case

Munit

To run a specific test case inside the unit test suite pass *exact-test-name* as an argument to scala-cli:

//> using test.dep org.scalameta::munit::0.7.29
package tests.only

class Tests extends munit.FunSuite {
test("bar") {
assert(2 + 2 == 5)
}
test("foo") {
assert(2 + 3 == 5)
}
test("foo-again") {
assert(2 + 3 == 5)
}
}
scala-cli test . --test-only 'tests.only*'  -- '*foo*'
# tests.only.Tests:
# + foo 0.045s
# + foo-again 0.001s

Test arguments

You can pass test arguments to your test framework by passing them after --:

//> using test.dep org.scalatest::scalatest::3.2.9

import org.scalatest._
import org.scalatest.flatspec._
import org.scalatest.matchers._

class Tests extends AnyFlatSpec with should.Matchers {
"A thing" should "thing" in {
assert(2 + 2 == 4)
}
}
scala-cli test MyTests.test.scala -- -oD
Compiling project (1 Scala source)
Compiled project
Tests:
A thing
- should thing (22 milliseconds)
Run completed in 359 milliseconds.
Total number of tests run: 1
Suites: completed 1, aborted 0
Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
All tests passed.