Skip to content

Repository files navigation

Gradle plugin for multi-backend Scala and sbt test frameworks

Summary

This is a Gradle plugin that adds functionality to the Gradle Scala plugin:

  • compile, run, and test Scala code using JVM, Scala.js, and Scala Native backends

  • shared ("cross-compile") code between backends

  • include sources for specific Scala version

  • run the sbt test frameworks this plugin supports (see Test Frameworks)

  • access Scala backend and Scala version data from the build script.

Plugin integrates with:

  • Gradle test task configuration, test filtering, tagging, logging and reporting

  • IntelliJ test running and reporting

  • IntelliJ Scala plugin to correctly handle sources shared between backends.

Plugin works with:

  • Gradle 9.8.1

  • Scala.js 1.22.0

  • Scala Native 0.5.12

  • Node.js 26.10.0.

Plugin:

  • adds necessary backend-specific dependencies

  • adds necessary backend-specific Scala compiler plugins (for main and test code)

  • adds necessary backend-specific Scala compiler parameters

  • for Scala.js and Scala Native, adds link tasks

  • for Scala.js, retrieves and installs the configured version of Node.js

  • for Scala.js, installs the configured Node.js modules using npm

  • augments the test task to run those sbt test frameworks

  • includes sources and resources shared between backends

  • includes sources and resources for specific Scala version

  • configures project artifacts to include shared code when needed

  • configures names of the project artifact in accordance with the accepted conventions

  • exposes, via scalaBackend extension, data about the Scala backend and Scala version for use in the build script.

Plugin is written in Scala 3, but the project that the plugin is applied to can use Scala 3, 2.13 or 2.12; however, plugin is not compatible with Gradle plugins written in Scala 2.12.

Gradle build file snippets below use the Groovy syntax, not the Kotlin one.

Accompanying example project that shows off some of the plugin’s capabilities is available: cross-compile-example.

Quick start

Three builds cover the usual cases. Plugin id is org.podval.tools.scalajs. Previously, id org.podval.tools.scalajs was used; it is still available.

JVM tests

plugins {
  id 'org.podval.tools.scala' version '2.3.0'
}

scala.scalaVersion = '3.10.0'

dependencies {
  testImplementation scalaBackend.testFramework(org.podval.tools.test.framework.MUnit)
}

Do not call useJUnit, useTestNG, or useJUnitPlatform on test. The plugin registers its own test framework.

Scala.js application

Put the backend in gradle.properties (or pass -Porg.podval.tools.backend=js). build.gradle cannot select it. See Two extensions named scalaBackend.

org.podval.tools.backend=js
scalaVersion=3.10.0
plugins {
  id 'org.podval.tools.scala' version '2.3.0'
}

scala.scalaVersion = scalaVersion

link {
  moduleInitializers {
    main {
      className = 'example.Main'
    }
  }
}

./gradlew run links and runs that main class on Node.js.

Mixed backends

Backend directories have to be Gradle projects. The settings plugin includes js, jvm, native, shared, and partial shares such as js-native.

// settings.gradle
plugins {
  id 'org.podval.tools.scala.settings' version '2.3.0'
}

scalaBackend.includeBackendProjects()
// build.gradle — the mixed project
plugins {
  id 'org.podval.tools.scala' version '2.3.0'
}

scala.scalaVersion = '3.10.0'

scalaBackend {
  useArtifactSuffix = false
}
// js/build.gradle — one backend
dependencies {
  testImplementation scalaBackend.testFramework(org.podval.tools.test.framework.MUnit)
}

The mixed project’s scalaBackend block only carries useArtifactSuffix. Each backend project inherits that value. jvm, js, native, and the dependency helpers exist on the backend project, in js/build.gradle and its siblings. For a submodule rather than the root, include 'core' before includeBackendProjects().

Configuration

Plugin Id

Plugin is published on the Gradle Plugin Portal; to apply it to a Gradle project:

plugins {
  id 'org.podval.tools.scala' version '2.3.0'
}

(Plugin id org.podval.tools.scala can also be used.)

Plugin will automatically apply the Scala plugin to the project, so there is no need to manually list id 'scala' in the plugins block - but there is no harm in it either.

The same project plugin is also published as org.podval.tools.scala.

A settings plugin, org.podval.tools.scala.settings, selects the default backend and can include backend projects. Apply it in settings.gradle. This scalaBackend is not the project extension. See Two extensions named scalaBackend.

plugins {
  id 'org.podval.tools.scala.settings' version '2.3.0'
}

Scala Version

Project using the plugin has to specify a version of Scala for the Scala Gradle plugin to use.

One way to do it is to add Scala library dependency explicitly, and let the Scala plugin infer the Scala version from it:

dependencies {
  implementation "org.scala-lang:scala3-library_3:3.10.0"
}

Another way is to set the Scala version on the Scala plugin’s extension scala, and let the Scala plugin add appropriate Scala library dependency automatically:

scala.scalaVersion = scalaVersion

The latter approach:

  • is cleaner

  • is the future: the old, inference-based approach is going away (slowly; deprecated in Gradle 9)

  • allows the Scala version to be consistent across the modules of a multi-module project by using a gradle.properties file:

scalaVersion=3.10.0
  • allows the Scala version to be overridden from the command line:

$ ./gradlew -PscalaVersion={version-scala}

Plugin assumes that the project uses the explicit approach; no assumptions are made about the name of the property.

Starting with Scala 3.8.0, the Scala library is built for Java 17. When the project uses Scala 3.8 or newer, the plugin adds -release:17 to each ScalaCompile task that does not already set -release or -java-output-version.

Building for Specific Scala Version

Plugin does not support building for multiple Scala versions at the same time using only Gradle (unlike the Gradle Scala Multi-Version Plugin): I believe that "build matrix" belongs in the Continuous Integration tools, not in build tools.

Plugin does provide, in a Gradle-native way, functionality that helps build for different Scala versions one at a time from outside Gradle; see Sources Specific to Scala Version, Scala Backend Extension.

To run tests for specific Scala version (for instance, in a CI pipeline):

./gradlew clean check -PscalaVersion=3.10.0

To run tests for multiple Scala versions:

for v in '3.10.0' '2.13.18' '2.12.21'; do ./gradlew clean check -PscalaVersion=$v; done

To publish artifacts for multiple Scala versions:

for v in '3.10.0' '2.13.18' '2.12.21'; do ./gradlew clean publish -PscalaVersion=$v; done

Sources Specific to Scala Version

Alongside the usual Scala source root scala, as in src/main/scala and src/test/scala, plugin includes sources from Scala source roots specific to the Scala version in use; for Scala version x.y.z, additional Scala source roots are:

  • scala-x.y.z

  • scala-x.y

  • scala-x.

Similarly, alongside the usual resource root resources, as in src/main/resources and src/test/resources, plugin includes resources from resource roots specific to the Scala version in use; for Scala version x.y.z, additional resource roots are:

  • resources-x.y.z

  • resources-x.y

  • resources-x.

Additional Scala sources and resources are included both in Scala compilation and archives that package Scala sources.

This applies to Scala sources shared between the backends too.

Since only sources and resources appropriate to the Scala version in use are added, to work on the version-specific sources and resources in the IDE, you need to set the Gradle property that selects the Scala version and re-load the project in the IDE.

Dependencies

Plugin automatically adds to various Gradle configurations dependencies needed to support the backend used (if they were not added explicitly).

Unless you want to override versions of some of those dependencies, the only dependencies you need to add to the project are the test framework(s) that you use.

As usual, artifact names have suffixes corresponding to the Scala version: _3, _2.13 or _2.12. For the artifacts compiled by the non-JVM backends, before the Scala version another suffix indicating the backend is inserted: for Scala.js - _sjs1, for Scala Native - _native0.5.

A release such as 3.10.0 or 2.13.18 uses that binary suffix. Any other version uses the full Scala version: 3.8.0-RC1 becomes _3.8.0-RC1, and _sjs1_3.8.0-RC1 or _native0.5_3.8.0-RC1 on the other backends. The same string is the Scala suffix in dependency notation. scalaBinaryVersion stays the binary prefix.

For details on what dependencies are relevant for which backend, see: Scala.js Dependencies, Scala Native Dependencies, Test Frameworks Dependencies.

Two extensions named scalaBackend

The name scalaBackend is used twice. The two objects are not the same type, and an assignment that works on one does nothing, or fails, on the other.

The settings extension is created by org.podval.tools.scala.settings, in settings.gradle. It has backend (a string: js, jvm, native, or the longer names Scala.js, Scala Native, and JVM) and includeBackendProjects(). That backend value is the default for a single-backend build. A project property, including -P, overrides it. build.gradle runs too late to choose the backend: task classes are already selected.

The project extension is created by the plugin on a project that has one backend. Its backend property is that backend, already chosen. Assigning scalaBackend.backend = 'js' in build.gradle does not switch backends. The booleans jvm, js, and native report the choice. So do name (JVM, Scala.js, Scala Native) and sourceRoot (jvm, js, native).

On a mixed project the project extension only has useArtifactSuffix. scalaBackend.jvm on that project fails. The booleans and the dependency helpers are on js, jvm, and native.

Scala Backend Extension

On a single-backend project, the project extension exposes the Scala version and the Scala backend. Build scripts use it to declare dependencies for that backend and that Scala version.

The extension exposes:

  • boolean properties: jvm, js, native, scala3, nonJvmJUnit4present

  • the Scala version: scalaVersion, scalaBinaryVersion, scala2BinaryVersion

  • the backend: name, sourceRoot, suffix, backendVersion

  • testFramework(frameworkClass) and testFramework(frameworkClass, version), where frameworkClass is one of the classes listed under Test Frameworks (for example org.podval.tools.test.framework.MUnit)

  • scalaDependency(group, artifact, version) and scalaDependency(group, artifact, version, transformer)

  • useArtifactSuffix, default true, controls the suffix of the artifacts this project produces. Set it in the build script, or in an afterEvaluate block written in that same build script. The plugin reads it at the end of this project’s configuration, after that block, and before other projects can resolve this project’s jar. On a mixed build, set it on the mixed project, in that project’s build script or in its afterEvaluate. The parent is configured before backend children. Each backend inherits the value. A value in the backend project’s own build script, including its afterEvaluate, wins. An afterEvaluate registered from inside a build-script afterEvaluate does not quietly lose. The flag is already finalized, and the assignment fails configuration with IllegalStateException (The value for %s is final and cannot be changed any further.). When the value is omitted or true, the main jar, the sources jar, and the javadoc jar use suffix, and a Maven publication whose artifactId is still the project name is published with that suffix. When it is false, those jars and that artifactId use the project name and no suffix. Scala.js and Scala Native jars do not get a js or native appendix in either mode. suffix remains the suffix used in dependency notation and does not follow this flag.

scalaBackend {
  useArtifactSuffix = false
}

Transformer can indicate that a particular dependency is:

  • only available for Scala 3: scala3()

  • only available for Scala 2: scala2()

  • only available for JVM: jvm()

  • a Scala compiler plugin: scalaCompilerPlugin()

  • a compound version such as 3.10.0+1.22.0: versionCompound();

Kotlin

The snippets in the rest of this manual are Groovy. Kotlin keeps the is prefix that Groovy drops, so the booleans are isJvm, isJs, isNative, and isScala3.

A Scala.js test project. scalaBackend.backend here is the settings extension. In build.gradle.kts, scalaBackend is the project extension.

// settings.gradle.kts
plugins {
  id("org.podval.tools.scala.settings") version "2.3.0"
}

scalaBackend.backend.set("js")
// build.gradle.kts
plugins {
  id("org.podval.tools.scala") version "2.3.0"
}

scala {
  scalaVersion.set(providers.gradleProperty("scalaVersion"))
}

dependencies {
  testImplementation(scalaBackend.testFramework(org.podval.tools.test.framework.MUnit::class.java))
  implementation(scalaBackend.scalaDependency("org.scala-js", "scalajs-dom", "2.8.1"))
}

scalaDependency’s transformer is a Groovy `Closure, so a Kotlin lambda does not match it. The three-argument method above is the one to call from Kotlin. When a transformer is required, pass a KotlinClosure1 that returns the ScalaDependency:

import org.gradle.kotlin.dsl.KotlinClosure1
import org.podval.tools.build.ScalaDependency

scalaBackend.scalaDependency(
  "org.scala-js",
  "scalajs-library",
  "1.22.0",
  KotlinClosure1<ScalaDependency, ScalaDependency>({ it.scala2().jvm() })
)

The plugin already adds that library dependency. The closure form is for a dependency you declare yourself.

scalaBackend {
  useArtifactSuffix.set(false)
}

link {
  moduleKind.set("CommonJSModule")
  moduleInitializers {
    create("main") {
      className.set("example.Main")
    }
  }
}

node {
  version.set("26.10.0")
  modules.set(listOf("jsdom"))
}

On a mixed build, includeBackendProjects() is called on the settings extension:

// settings.gradle.kts
scalaBackend.includeBackendProjects()

Scala Backends

Plugin can be applied to:

Single Backend: JVM

Plugin, its name notwithstanding, provides benefits even if applied to a project that uses only Scala, without Scala.js or Scala Native, namely: the supported sbt test frameworks (see Test Frameworks).

For the list of test frameworks supported by the plugin, see Test Frameworks.

To use the plugin in such a way, build.gradle file for the project, in addition to applying the plugin and setting the Scala version, needs to list in the dependencies.testImplementation the test framework(s) used.

Configuration of the test task cannot have useJUnit.

Any Gradle plugins providing integration with specific test frameworks must be removed from the project: plugin itself provides integration with test frameworks, in some cases - better than the dedicated test-framework-specific plugins ;)

Single Backend: Scala.js or Scala Native

Sources under src are processed with one specific backend; backend used is selected by the project property org.podval.tools.backend.

The value of this property is treated as case-insensitive.

Set this property in gradle.properties, or pass it with -P. Setting it in build.gradle won’t work.

If this property is set to Scala.js or js, Scala.js backend is used.

If this property is set to Scala Native or native, the Scala Native backend is used.

If this property is set to JVM or not set at all, JVM backend is used, making this setup equivalent to the Single Backend: JVM one.

The same choice can be made on the settings extension scalaBackend, in settings.gradle. A project property, including one passed with -P, overrides that value. build.gradle cannot choose the backend: the plugin selects task classes while it is applied, before the rest of the build script runs.

plugins {
  id 'org.podval.tools.scala.settings' version '2.3.0'
}

scalaBackend {
  backend = 'js'
}

backend in that block is a string on the settings extension. It is not the project extension’s backend. See Two extensions named scalaBackend.

For example, to use Scala.js backend for the project, put the following into the gradle.properties file of the project:

org.podval.tools.backend=js

Mixed Backends

Plugin supports using multiple backends in the same project with some sources shared between some of them.

This mode is triggered when at least one of the directories containing backend-specific sources - js, jvm, native - exists. All backends do not have to be used all the time; with only one backend used, this setup is equivalent to the Single Backend: Scala.js or Scala Native one (and if that backend is jvm - to the Single Backend: JVM one). Backend-specific directories must also be included as projects in the settings.gradle file.

After the projects that own those directories are included, scalaBackend.includeBackendProjects() includes each child directory named js, jvm, native, shared, or a partial share such as js-native. It does not rename projects. Call it at the end of settings.gradle.

plugins {
  id 'org.podval.tools.scala.settings' version '2.3.0'
}

include 'core'

scalaBackend.includeBackendProjects()

When core/js exists, that call includes :core:js.

To share sources (include them in the backend-specific compilation together with the backend-specific sources) between all the backends, place them in the directory shared - or directly in the src directory of the overall mixed-backend project; to avoid confusion, only one of those locations should be used, although plugin currently does not enforce this restriction :)

To share sources between some of the backends (partial sharing), place them in a directory with the name listing the backends the sources are to be shared between:

  • in any order

  • separated by -

  • with optional shared- prefix

  • jvm-js-native and shared-jvm-js-native are not allowed - use shared instead

  • shared-jvm, shared-js and shared-native are not allowed - use jvm, js, native instead

  • jvm-jvm and other duplicate backend names are not allowed

  • js-native and native-js and other pairs of directories that share sources between the same set of backends are not allowed - pick one ;)

Shared directories must also be included as projects in the settings.gradle file; strictly speaking, they do not have to be for the Gradle build to work correctly, but for the shared sources to be recognized in IntelliJ they must be; for simplicity, plugin requires that they always are.

Gradle project names of the subprojects can be changed, but the directory names (js, jvm, native, shared, js-jvm, shared-js-native) cannot: plugin looks up the subprojects by their directory names, not by their project names.

Build script for the overall project (or module) is where:

  • plugin is applied

  • Scala version is set

  • useArtifactSuffix is set, once, for every backend project

  • any build logic that applies to the overall project resides.

Build scripts in the backend-specific directories are where:

  • backend-specific dependencies (including test frameworks) are added

  • backend-specific tasks (including link and test) are configured

  • any build logic that applies only to specific backend resides.

Shared directories hold sources fully or partially shared between the backends. There is no need (nor point) to have a build.gradle file in any of the shared directories: they are just containers for the sources shared between the backends.

In this mode, plugin:

  • applies itself to subprojects, backend-specific and shared (so there is no need to apply it manually in the subproject’s build.gradle)

  • propagates the Scala version set in the overall project’s build.gradle to subprojects (so there is no need to set it manually in the subproject’s build.gradle)

  • configures appropriate backend for each of the backend-specific subprojects

  • disables all source and archive tasks and unregisters all Scala sources in the overall project

  • disables all tasks in the shared subproject.

Project layout for such setup is:

project (7)
+--- settings.gradle (1)
+--- build.gradle (2)
+--- src (4)
+--- shared
|    \--- src (4)
+--- js-jvm
|    \--- src (5)
+--- js-native
|    \--- src (5)
+--- jvm-native
|    \--- src (5)
+--- js
|    +--- build.gradle (3)
|    \--- src (6)
+--- jvm
|    +--- build.gradle (3)
|    \--- src (6)
\--- native
     +--- build.gradle (3)
     \--- src (6)
  1. settings file where backend-specific and shared subprojects are included

  2. build script of the overall project

  3. build scripts of the backend-specific projects

  4. sources shared between all backends

  5. sources shared between some backends

  6. sources specific to a backend

  7. src here is shared between all backends, the same role as shared/src. Use one of the two.

Publish from the command line. When the build runs inside IntelliJ, the plugin adds an implementation dependency from each backend project to its shared projects so navigation works. That dependency is written into the POM if you publish from the IDE.

JVM

Dependencies

When running on JVM, plugin adds SBT Test Interface org.scala-sbt:test-interface:1.0 to the testRuntimeOnly configuration: it is used by the plugin to run the tests, and is normally brought in by the test frameworks themselves, but since ScalaTest does not bring it in, plugin adds it.

dependencies {
  testRuntimeOnly 'org.scala-sbt:test-interface:1.0'
}

Scala.js

Dependencies

If org.scala-js:scalajs-library dependency is specified explicitly, plugin uses its version for other Scala.js dependencies that it adds.

Plugin creates scalajs configuration for Scala.js dependencies used by the plugin itself.

The table below lists what is added to what configurations.

Name group:artifact Backend Configuration Notes

Compiler Plugin

org.scala-js:scalajs-compiler

JVM Scala 2

scalaCompilerPlugins

only for Scala 2

JUnit Compiler Plugin

org.scala-js:scalajs-junit-test-plugin

JVM Scala 2

testScalaCompilerPlugins

only for Scala 2 and only if JUnit4 for Scala.js is used

Linker

org.scala-js:scalajs-linker

JVM Scala 2

scalajs

Node.js JavaScript environment with JSDOM

org.scala-js:scalajs-env-jsdom-nodejs

JVM Scala 2

scalajs

Test Adapter

org.scala-js:scalajs-sbt-test-adapter

JVM Scala 2

scalajs

Scala Library for Scala.js

org.scala-lang:scala3-library

Scala.js

implementation

only for Scala 3

Library

org.scala-js:scalajs-library

JVM Scala 2

implementation

DOM Library

org.scala-js:scalajs-dom

Scala.js

implementation

Test Bridge

org.scala-js:scalajs-test-bridge

JVM Scala 2

testRuntimeOnly

scalaDependency and pluginDependency build the same notations. The plugin adds these dependencies itself. The same methods declare a dependency of your own:

dependencies {
  implementation scalaBackend.scalaDependency('org.scala-js', 'scalajs-library', '1.22.0', {it.scala2().jvm()}) // sets scalaBackend.backendVersion
  implementation scalaBackend.scalaDependency('org.scala-js', 'scalajs-dom', '2.8.1')
  if (scalaBackend.scala3) {
    implementation scalaBackend.scalaDependency('org.scala-lang', 'scala3-library', scalaBackend.scalaVersion, {it.scala3()})
  }
  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-linker', scalaBackend.backendVersion, {it.scala2()})
  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-sbt-test-adapter', scalaBackend.backendVersion, {it.scala2()})
  scalajs scalaBackend.pluginDependency('org.scala-js', 'scalajs-env-jsdom-nodejs', '1.1.1', {it.scala2()})
  if (!scalaBackend.scala3) {
    scalaCompilerPlugins scalaBackend.scalaDependency('org.scala-js', 'scalajs-compiler', scalaBackend.backendVersion, {it.scala2().scalaCompilerPlugin()})
  }
  if (!scalaBackend.scala3 && scalaBackend.nonJvmJUnit4present) {
    testScalaCompilerPlugins scalaBackend.scalaDependency('org.scala-js', 'scalajs-junit-test-plugin', scalaBackend.backendVersion, {it.scala2().scalaCompilerPlugin()})
  }
  testRuntimeOnly scalaBackend.scalaDependency('org.scala-js', 'scalajs-test-bridge', scalaBackend.backendVersion, {it.scala2().jvm()})
}

Compiling

To support Scala.js, Scala compiler needs to be configured to produce both the class and sjsir files.

Scala 3

If the project uses Scala 3, all it takes is to pass -scalajs option to the Scala compiler, since Scala 3 compiler has Scala.js support built in:

tasks.withType(ScalaCompile) {
  scalaCompileOptions.with {
    additionalParameters = [ '-scalajs' ]
  }
}

Plugin automatically adds this option to the main and test Scala compilation tasks if it is not present.

Scala 2

If the project uses Scala 2, Scala.js compiler plugin dependency needs to be declared:

dependencies {
  scalaCompilerPlugins "org.scala-js:scalajs-compiler_$scalaVersion:1.22.0"
}

Plugin does this automatically unless a dependency on org.scala-js:scalajs-compiler is declared explicitly.

If the project uses Scala 2 and JUnit 4 for Scala.js, a JUnit Scala compiler plugin is also needed ([junit4-scalajs-scalanative]):

dependencies {
  testScalaCompilerPlugins "org.scala-js:scalajs-junit-test-plugin_$scalaVersion:1.22.0"
}

Plugin adds this automatically also.

There is no need to add -Xplugin: Scala compiler parameters for the compiler plugins.

Linking

For linking of the main code, plugin adds link task of type org.podval.tools.scalajs.ScalaJSLinkTask.Main; all tasks of this type automatically depend on the classes task.

For linking of the test code, plugin adds testLink task of type org.podval.tools.scalajs.ScalaJSLinkTask.Test; all tasks of this type automatically depend on the testClasses task.

Link tasks expose a property JSDirectory that points to a directory with the resulting JavaScript, so that it can be, for example, copied where needed:

link.doLast {
  project.sync {
    from link.JSDirectory
    into jsDirectory
  }
}

Link tasks have a number of properties that can be used to configure linking. Configurable properties with their defaults are:

link {
  optimization     = 'Fast'          // one of: 'Fast', 'Full'
  moduleKind       = 'NoModule'      // one of: 'NoModule', 'ESModule', 'CommonJSModule'
  moduleSplitStyle = 'FewestModules' // one of: 'FewestModules', 'SmallestModules', 'SmallModulesFor'
  // when using `specs2` testing framework, '2018' and later is required:
  // it supports regular expressions used in many matchers using strings
  esVersion        = '2015'          // one of '2015'..'2026'
  smallModulesFor  = []              // list of packages; relevant only when moduleSplitStyle = 'SmallModulesFor'
  prettyPrint      = false
  useWebAssembly   = false           // emit WebAssembly (see below for constraints)
  useJSPI          = false           // enable js.async / js.await (needs a JSPI-capable JS engine)
}

Setting optimization to Full enables:

  • Semantics.optimized

  • checkIR

  • the Closure Compiler, unless moduleKind is ESModule (Closure does not support ES modules, and Scala.js has deprecated this use).

When useWebAssembly = true, the following are required (see https://www.scala-js.org/doc/project/webassembly.html):

  • moduleKind = 'ESModule' - moduleSplitStyle = 'FewestModules' - esVersion of at least '2022'

useJSPI enables Scala.js JSPI (js.async / js.await) and needs a JS engine that supports JSPI. Environment variables SCALAJS_USE_WEBASSEMBLY and SCALAJS_USE_JSPI can override the defaults.

For ScalaJSLinkTask.Main tasks, a list of module initializers may also be configured:

moduleInitializers {
  main {
    className = '<fully qualified class name>'
    mainMethodName = 'main'
    mainMethodHasArgs = false
  }
}

Name of the module initializer ('main' in the example above) becomes the module id.

Running

Plugin adds run task for running the main code (if it is an application and not a library); this task automatically depends on the link task.

Additional tasks of type org.podval.tools.scalajs.ScalaJSRunTask.Main can be added manually. Set linkTask to the link task that produces the JavaScript. The plugin does not look through dependsOn while the task runs.

tasks.register('runDebug', org.podval.tools.scalajs.ScalaJSRunTask.Main) {
  linkTask = tasks.named('link')
}

link and test are cacheable. run is not.

JavaScript Environment

Both run and test tasks have a property jsEnv that selects a JavaScript environment to use:

run {
  jsEnv = 'Node.js' // one of: 'Node.js', 'Node.js+DOM'
}

PhantomJS is not supported: the project has been abandoned since 2018.

Selenium is not supported: the project seems to be abandoned.

Playwright ('io.github.gmkumar2005:scala-js-env-playwright_2.13:{version-scala-js-env-playwright}') is not supported: the project publishes artifacts only for Scala 2.12.

Node.js

For running Scala.js code and tests, plugin uses Node.js.

Plugin adds node extension to the project. This extension can be used to specify the version of Node.js to use and Node modules to install:

node {
  version = '26.10.0'
  modules = []
}

If the Node.js version is not specified, the plugin uses the Node.js already installed on the machine, or, if none is available, installs the default version (26.10.0). If a version is specified, the plugin installs that version. That choice is made when nodeSetup runs, not during configuration.

The nodeSetup task installs Node.js and runs npm init and npm install. run, test, node, and npm depend on nodeSetup. link does not. node and npm are not cacheable. package.json and node_modules stay in the project root.

Node.js distributions are installed under ~/.gradle/nodejs.

If you are using Node.js+DOM JavaScript environment (org.scala-js:scalajs-env-jsdom-nodejs), you need 'jsdom' module.

To get better traces, one can add source-map-support module.

If package.json does not exist, nodeSetup runs npm init private.

Plugin adds tasks node and npm for executing node and npm commands using the same version of Node.js that is used by the plugin; those tasks can be used from the command line like this:

./gradlew npm --npm-arguments 'version'
./gradlew node --node-arguments '...'

Scala Native

Dependencies

If org.scala-native:scala3lib (for Scala 3) or org.scala-native:scalalib (for Scala 2) dependency is specified explicitly, plugin uses its version for all the Scala Native dependencies that it adds.

Plugin creates scalanative configuration for Scala Native dependencies used by the plugin itself.

The table below lists what is added to what configurations.

Name group:artifact Backend Configuration Notes

Compiler Plugin

org.scala-native:nscplugin

JVM

scalaCompilerPlugins

JUnit Compiler Plugin

org.scala-native:junit-plugin

JVM

testScalaCompilerPlugins

only if JUnit4 for Scala Native is used

Linker

org.scala-native:tools

JVM

scalanative

Test Adapter

org.scala-native:test-runner

JVM

scalanative

Library

org.scala-native:scala3lib

Scala Native

implementation

only for Scala 3

Library

org.scala-native:scalalib

Scala Native

implementation

only for Scala 2

Test Bridge

org.scala-native:test-interface

Scala Native

testRuntimeOnly

Native Library

org.scala-native:nativelib

Scala Native

implementation

C Library

org.scala-native:clib

Scala Native

implementation

Posix Library

org.scala-native:posixlib

Scala Native

implementation

Windows Library

org.scala-native:windowslib

Scala Native

implementation

Java Library

org.scala-native:javalib

Scala Native

implementation

Aux Library

org.scala-native:auxlib

Scala Native

implementation

scalaDependency and pluginDependency build the same notations. versionCompound() produces the scalaVersion+nativeVersion form used by scala3lib and scalalib. The plugin adds these dependencies itself:

dependencies {
  if (scalaBackend.scala3) {
    implementation scalaBackend.scalaDependency('org.scala-native', 'scala3lib', '0.5.12', {it.scala3().versionCompound()}) // sets scalaBackend.backendVersion
  } else {
    implementation scalaBackend.scalaDependency('org.scala-native', 'scalalib', '0.5.12', {it.scala2().versionCompound()}) // sets scalaBackend.backendVersion
  }
  implementation scalaBackend.scalaDependency('org.scala-native', 'nativelib', scalaBackend.backendVersion)
  implementation scalaBackend.scalaDependency('org.scala-native', 'clib', scalaBackend.backendVersion)
  implementation scalaBackend.scalaDependency('org.scala-native', 'posixlib', scalaBackend.backendVersion)
  implementation scalaBackend.scalaDependency('org.scala-native', 'javalib', scalaBackend.backendVersion)
  implementation scalaBackend.scalaDependency('org.scala-native', 'windowslib', scalaBackend.backendVersion)
  implementation scalaBackend.scalaDependency('org.scala-native', 'auxlib', scalaBackend.backendVersion)

  scalanative scalaBackend.pluginDependency('org.scala-native', 'tools', scalaBackend.backendVersion)
  scalanative scalaBackend.pluginDependency('org.scala-native', 'test-runner', scalaBackend.backendVersion)

  scalaCompilerPlugins scalaBackend.scalaDependency('org.scala-native', 'nscplugin', scalaBackend.backendVersion, {it.scalaCompilerPlugin()})

  if (scalaBackend.nonJvmJUnit4present) {
    testScalaCompilerPlugins scalaBackend.scalaDependency('org.scala-native', 'junit-plugin', scalaBackend.backendVersion, {it.scalaCompilerPlugin()})
  }

  testRuntimeOnly scalaBackend.scalaDependency('org.scala-native', 'test-interface', scalaBackend.backendVersion)
}

Compiling

To support Scala Native, Scala compiler needs to be configured to produce both the class and nir files.

The Scala Native compiler plugin dependency needs to be declared:

dependencies {
  scalaCompilerPlugins "org.scala-native:nscplugin_$scalaVersion:0.5.12"
}

Plugin does this automatically unless a dependency on org.scala-native:nscplugin is declared explicitly.

If the project uses JUnit 4 for Scala Native, a JUnit Scala compiler plugin is also needed ([junit4-scalajs-scalanative]):

dependencies {
  testScalaCompilerPlugins "org.scala-native:junit-plugin_$scalaVersion:0.5.12"
}

Plugin adds this automatically also.

There is no need to add -Xplugin: Scala compiler parameters for the compiler plugins.

Linking

For linking of the main code, plugin adds link task of type org.podval.tools.scalanative.ScalaNativeLinkTask.Main ; all tasks of this type automatically depend on the classes task.

For linking of the test code, plugin adds testLink task of type org.podval.tools.scalanative.ScalaNativeLinkTask.Test ; all tasks of this type automatically depend on the testClasses task.

Link tasks expose a property NativeDirectory that points to a directory with the Scala Native Linker output, so that it can be copied where needed.

Link tasks have a number of properties that can be used to configure linking. Configurable properties with their defaults are:

link {
  mode     = 'debug' // one of: 'debug', 'release-fast', 'release-size', 'release-full'
  lto      = 'none'  // one of: 'none', 'thin', 'full'
  gc       = 'immix' // one of: 'none', 'boehm', 'immix', 'commix'
  optimize = false
}

If not set explicitly, properties are set from the environment variables:

  • mode - SCALANATIVE_MODE - lto - SCALANATIVE_LTO - gc - SCALANATIVE_GC - optimize - SCALANATIVE_OPTIMIZE

For ScalaNativeLinkTask.Main tasks, property mainClass may also be configured. This is the class that will be run.

Running

Plugin adds run task for running the main code (if it is an application and not a library); this task automatically depends on the link task.

Additional tasks of type org.podval.tools.scalanative.ScalaNativeRunTask.Main can be added manually. Set linkTask to the link task that produces the binary. The plugin does not look through dependsOn while the task runs.

tasks.register('runDebug', org.podval.tools.scalanative.ScalaNativeRunTask.Main) {
  linkTask = tasks.named('link')
}

link and test are cacheable. run is not.

Testing

Test Task

Test task added by the plugin is derived from the normal Gradle test task, and can be configured in the traditional way - with some limitations:

  • plugin applies its own Gradle test framework (useSbt) to each test task; re-configuring the Gradle test framework (via useJUnit, useTestNG or useJUnitPlatform) is not supported

  • isScanForTestClasses must be at its default value true.

  • Scala.js and Scala Native tests must run in the same JVM where they are discovered, so they are not forked, and forking configuration is ignored.

Dry run (test.dryRun=true or --test-dry-run command line option) is supported.

Test filtering and tagging are supported to the extent that the individual test frameworks support them; see Test Filtering, Test Tagging and Test Frameworks.

If there is a need to have test runs with different configurations, more testing tasks can be added manually.

For JVM, the type of the test task is org.podval.tools.jvm.JvmTestTask. Any such task will automatically depend on the testClasses task (and testRuntimeClassPath).

For Scala.js the type of the test task is org.podval.tools.scalajs.ScalaJSRunTask.Test. Such test tasks have to depend on a ScalaJSLinkTask.Test task. The test task added by the plugin does it automatically; for manually added tasks this dependency has to be added manually.

For Scala Native the type of the test task is org.podval.tools.scalanative.ScalaNativeRunTask.Test. Such test tasks have to depend on a ScalaNativeLinkTask.Test task. The test task added by the plugin does it automatically; for manually added tasks this dependency has to be added manually.

Test Filtering

Gradle filters tests with three sets of patterns. includeTestsMatching and excludeTestsMatching are set on the test task. --tests is the command-line set.

IntelliJ sends two of these patterns: selected.package.* for a package, and fully.qualified.TestClass for a class. A single test method is fully.qualified.TestClass.test.

Inclusion rules:

  • If neither inclusions nor exclusions are specified, every test is included.

  • If only inclusions are specified, a test must match one of them.

  • If only exclusions are specified, a test must match none of them.

  • If both inclusions and exclusions are specified, a test must match one inclusion and no exclusion.

  • If both the build script and --tests specify inclusions, a test must match both.

When both the build script and --tests name test cases, a case is kept only when it satisfies both sides, and two wildcard patterns that do not contain each other select nothing.

The following patterns specify test classes to run:

  • "*": all tests, just as if no includes are specified

  • "*IntegrationTest": classes whose names end with "IntegrationTest"

  • "Scala*": classes whose name starts with "Scala"

  • "org.podval.tools.test.Scala*": classes in specified package whose name starts with "Scala"

  • "org.podval.tools.test.*": tests in specified package (used by IntelliJ Idea, see Testing in IntelliJ)

  • "org.podval.tools.test.ScalaTest": tests in specified class (used by IntelliJ Idea, see Testing in IntelliJ).

All these patterns work as intended.

The following patterns specify test cases to run:

  • "org.podval.tools.test.SomeTestClass.success": specified test case in specified class (used by IntelliJ Idea, see Testing in IntelliJ)

  • "org.podval.tools.test.SomeTestClass.succ*": test cases whose names start with "succ" in specified class.

How much of a test-case pattern a framework honors is described with that framework below. The sbt selector limits are in the implementation notes.

Test Tagging

Names of the tags to include and exclude in the run are specified in:

test {
  useSbt {
    includeCategories = ["itag1", "itag2"]
    excludeCategories = ["etag1", "etag2"]
  }
}

Inclusion rules are:

  • if no inclusions nor exclusions are specified, all tests are included.

  • if only inclusions are specified, only tests tagged with one of them are included.

  • if only exclusions are specified, only tests not tagged with any of them are included.

  • if both inclusions and exclusions are specified, only tests tagged with one of the inclusions and not tagged with any of the exclusions are included.

Skipped Tests

When running some test methods explicitly included by a filter, I do not want to see skipped methods mentioned in the test report just as I do not want to see other skipped test classes there.

I do want to see tests explicitly ignored in code (e.g., in ScalaTest, or JUnit4’s falsified assumptions).

During a dry run, though, I want to see everything that was skipped, including test classes that were skipped entirely; for such, a test case named dry run is reported as skipped.

Nested Test Suites

Some test frameworks have a notion of nested test suites, where nesting test class aggregates nested test classes.

Plugin supports such a scenario and, when test framework involved provides sufficient information about the tests run, attributes test cases from the nested suites to them: test report will have no test cases for the nesting class; instead, test cases will be reported for the nested classes they belong to.

Testing in IntelliJ

In the following, it is assumed that the IDE is configured to use Gradle to run tests etc.

On JVM, whatever you can run from Idea you can also debug; Scala.js code runs on Node.js, so there is no debugging it - breakpoints have no effect; nor do they on Scala Native.

As with any other Gradle project imported into Idea, you can run Gradle tasks.

IntelliJ lets you run objects with main methods using either:

  • object node in the project tree or - gutter icon in the object’s file

On Scala.js or Scala Native, objects can not be run this way: the code needs to be compiled and linked for the appropriate backend. This is what the run task added by the plugin is for.

As usual, when you run tests:

  • results are displayed in tree form - test counts are displayed.

Note: if the test name in the sbt.testing.Event that IntelliJ receives starts with the name of the type the test belongs to, IntelliJ drops this prefix - probably to accommodate JUnit4, which incorrectly prepends all test names with the name of their class. As a result, for frameworks that have a notion of named suite (ZIO Test and ScalaCheck), if the name of the suite is the same as the name of the type, incorrectly IntelliJ drops it.

As usual, you can run all tests from the project tree using any of the nodes:

<root>
  src
    test
      scala

As usual, you can run all tests from a package using the package’s node in the project tree. Idea supplies Gradle test filter "selected.package.*".

As usual, you can run individual test class for the frameworks Idea recognizes using either:

  • test’s node in the project tree or - gutter icon in the test’s file

Idea supplies Gradle test filter "fully.qualified.TestClass".

As usual, you can run individual test in a test class for the frameworks Idea recognizes using:

  • gutter icon in the test’s file

Idea supplies Gradle test filter "fully.qualified.TestClass.test".

From the test frameworks this plugin supports, Idea recognizes:

  • JUnit4 - JUnit4 for Scala.js - JUnit4 for Native

Scala plugin for Idea recognizes:

  • MUnit - ScalaTest - Specs2 - uTest

Weaver Test test objects are recognized by IntelliJ as tests (because weaver.RunnableSuite is annotated with org.junit.runner.RunWith): you get a gutter icon for the test object, which lets you run or debug it, and reflects the results of the previous run; there are no gutter icons for the individual tests, and even if there were, Weaver Test ignores test selectors ;)

ScalaCheck and ZIO Test are not recognized by the Scala Plugin: no gutter icon for the test class nor individual tests in it are available, Run and Debug commands are not available in the context menu of the test classes node in the Project tree and of the gutter icon of the test class.

Since Hedgehog and ZIO Test tests are objects with main method, they can be run from Idea (on JVM), but there is no test result tree nor test counts displayed, and since Gradle is not involved, no test reports.

Test Frameworks

Plugin replaces the test task with one that runs the sbt test frameworks listed below. A framework that is not in that list is not picked up from the classpath. Several of the listed frameworks can be used at the same time.

Various test frameworks are listed or recognized by:

Framework Recognized by sbt Recognized by IntelliJ IDEA Recognized by IntelliJ Scala Plugin Listed by Scala.js Works with this Plugin

AirSpec

no

no

no

yes

yes

Greenlight

no

no

no

yes

no: defunct

Hedgehog

yes

no

no

no

yes

JUnit4

yes

yes

no

yes

yes; reimplemented for Scala.js and Scala Native

JUnit5

no

yes

no

no

no: own test discovery; no point: JVM only

MiniTest

no

no

no

yes

no: defunct

MUnit

yes

yes

yes

yes

Nyaya

no

no

no

yes

no: defunct

ScalaCheck

yes

no

no

yes

yes

Scalaprops

no

no

no

yes

yes

ScalaTest

yes

no

yes

yes

yes

Scala Test-State

no

no

no

yes

no: defunct

specs

yes

no

no

no

no: defunct, use specs2

specs2

yes

no

yes

no

yes

TestNG

no

yes

no

no

no: SBT interface defunct; no point: JVM only

uTest

no

no

yes

yes

yes

Weaver Test

yes

yes

no

no

yes

ZIO test

yes

no

no

no

yes

Framework-specific information for the frameworks that are supported follows.

Dependencies

Name group:artifact Backends Version Notes

JUnit4

com.github.sbt:junit-interface

jvm

0.13.3

Java

JUnit4 for Scala.js

org.scala-js:scalajs-junit-test-runtime

js

1.22.0

Scala 2

JUni4 for Scala Native

org.scala-native:junit-runtime

native

0.5.12

AirSpec

org.wvlet.airframe:airspec

jvm, js, native

2026.2.2

Scala Native only on Scala 3

Hedgehog

qa.hedgehog:hedgehog-sbt

jvm, js, native

0.15.0

MUnit

org.scalameta:munit

jvm, js, native

1.3.6

ScalaCheck

org.scalacheck:scalacheck

jvm, js, native

1.20.0

Scalaprops

com.github.scalaprops:scalaprops

jvm

0.11.1

currently not supported on Scala.js nor Scala Native

ScalaTest

org.scalatest:scalatest

jvm, js, native

3.2.20

specs2

org.specs2:specs2-core

jvm, js, native

5.9.1

latest that supports Scala 2: 4.23.0

uTest

com.lihaoyi:utest

jvm, js, native

0.9.5

Weaver Test

org.typelevel:weaver-cats

jvm, js, native

0.13.0

ZIO Test

dev.zio:zio-test-sbt

jvm, js, native

2.1.26

testFramework builds the notation for one of the frameworks above. Pass the framework class, and optionally a version. Omit the version to use the version shipped with this plugin:

import org.podval.tools.jvm.JUnit4Jvm
import org.podval.tools.scalajs.JUnit4ScalaJS
import org.podval.tools.scalanative.JUnit4ScalaNative
import org.podval.tools.test.framework.*

dependencies {
  if (scalaBackend.jvm) {
    testImplementation scalaBackend.testFramework(JUnit4Jvm, '0.13.3')
  }
  if (scalaBackend.js) {
    testImplementation scalaBackend.testFramework(JUnit4ScalaJS, '1.22.0')
  }
  if (scalaBackend.native) {
    testImplementation scalaBackend.testFramework(JUnit4ScalaNative, '0.5.12')
  }
  if (!scalaBackend.native || scalaBackend.scala3) {
    testImplementation scalaBackend.testFramework(AirSpec, '2026.2.2')
  }
  testImplementation scalaBackend.testFramework(Hedgehog, '0.15.0')
  testImplementation scalaBackend.testFramework(MUnit, '1.3.6')
  testImplementation scalaBackend.testFramework(ScalaCheck, '1.20.0')
  if (scalaBackend.jvm) {
    testImplementation scalaBackend.testFramework(Scalaprops, '0.11.1')
  }
  testImplementation scalaBackend.testFramework(ScalaTest, '3.2.20')
  if (!scalaBackend.scala3) {
    testImplementation scalaBackend.testFramework(Specs2, '4.23.0')
  } else {
    testImplementation scalaBackend.testFramework(Specs2, '5.9.1')
  }
  testImplementation scalaBackend.testFramework(UTest, '0.9.5')
  testImplementation scalaBackend.testFramework(WeaverTest, '0.13.0')
  testImplementation scalaBackend.testFramework(ZioTest, '2.1.26')
}

You do not have to specify test framework versions explicitly; to use the latest versions available at the time the version of the plugin you are using was released, above can be simplified further:

import org.podval.tools.jvm.JUnit4Jvm
import org.podval.tools.scalajs.JUnit4ScalaJS
import org.podval.tools.scalanative.JUnit4ScalaNative
import org.podval.tools.test.framework.*

dependencies {
  if (scalaBackend.jvm) {
    testImplementation scalaBackend.testFramework(JUnit4Jvm)
  }
  if (scalaBackend.js) {
    testImplementation scalaBackend.testFramework(JUnit4ScalaJS)
  }
  if (scalaBackend.native) {
    testImplementation scalaBackend.testFramework(JUnit4ScalaNative)
  }
  if (!scalaBackend.native || scalaBackend.scala3) {
    testImplementation scalaBackend.testFramework(AirSpec)
  }
  testImplementation scalaBackend.testFramework(Hedgehog)
  testImplementation scalaBackend.testFramework(MUnit)
  testImplementation scalaBackend.testFramework(ScalaCheck)
  if (scalaBackend.jvm) {
    testImplementation scalaBackend.testFramework(Scalaprops)
  }
  testImplementation scalaBackend.testFramework(ScalaTest)
  testImplementation scalaBackend.testFramework(Specs2)
  testImplementation scalaBackend.testFramework(UTest)
  testImplementation scalaBackend.testFramework(WeaverTest)
  testImplementation scalaBackend.testFramework(ZioTest)
}

Junit4

JUnit4 SBT interface (com.github.sbt:junit-interface) is a separate project from JUnit4 itself; SBT interface dependency brings in the underlying framework dependency junit:junit transitively; its version can be overridden in the Gradle build script.

  • test filtering: works fine

  • ignoring a test: not supported

  • assumptions: if falsified, result in a test being skipped: org.junit.Assume.assumeTrue(false);

Test Tagging

Tag tests with classes or traits that do not have to be derived from anything JUnit4 -specific; in the Gradle build file, excludeCategories and includeCategories list fully-qualified names of tagging classes or traits:

trait IncludedTest
trait ExcludedTest
@org.junit.experimental.categories.Category(Array(
  classOf[org.podval.tools.test.IncludedTest],
  classOf[org.podval.tools.test.ExcludedTest]
))
@Test def excluded(): Unit = ()

Nested Suites

JUnit4 uses an annotation on the nesting suite to indicate that it contains nested suites:

@org.junit.runner.RunWith(classOf[org.junit.runners.Suite])

and another annotation that lists the nested suites:

@org.junit.runners.Suite.SuiteClasses(Array(
  classOf[JUnit4Nested]
))

For example, JUnit4Nesting contains JUnit4Nested:

@org.junit.runner.RunWith(classOf[org.junit.runners.Suite])
@org.junit.runners.Suite.SuiteClasses(Array(
  classOf[JUnit4Nested]
))
class JUnit4Nesting {
}

import org.junit.Test
import org.junit.Assert.assertTrue

final class JUnit4Nested {
  @Test def success(): Unit = assertTrue("should be true", true)
  @Test def failure(): Unit = assertTrue("should be true", false)
}

By default, JUnit4 's sbt framework ignores the org.junit.runners.Suite runner; plugin supplies an appropriate argument to enable it.

By default, JUnit4 does not produce summary of the test run; plugin supplies an appropriate argument to enable it.

JUnit4 for Scala.js

JUnit4 for Scala.js is a framework distinct from JUnit4: it is a partial translation/re-implementation of JUnit4 circa 2015 and has different capabilities.

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • ignoring tests: not supported

  • assumptions: not supported;

JUnit4 for Scala Native

JUnit4 for Scala Native is a framework distinct from JUnit4: it is a port of the JUnit4 for Scala.js, which is a partial translation/re-implementation of JUnit4 circa 2015 and has different capabilities.

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • ignoring tests: not supported

  • assumptions: not supported;

AirSpec

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test: not supported;

Hedgehog

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test: not supported;

MUnit

  • test filtering: works fine on JVM; on Scala.js, does not support test case selectors and runs all test cases in the class

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test works: test("test".ignore) {};

MUnit uses JUnit internally, and transitively brings in the underlying framework dependency (whose version can be overridden in the Gradle build script):

  • on JVM - junit:junit

  • on Scala.js - org.scala-js:scalajs-junit-test-runtime

  • on Scala Native - org.scala-native:junit-runtime.

By default, MUnit does not produce summary of the test run; plugin supplies an appropriate argument to enable it.

Test Tagging

MUnit is based on JUnit4, so it supports the Category -based exclusion and inclusion; since on Scala.js MUnit uses JUnit4 for Scala.js, which does not support this mechanism, MUnit does not support it either.

Plugin does not use Category -based mechanism; MUnit provides a different, Tag -based mechanism, and that is what plugin uses.

Tag tests with values that are instances of munit.Tag:

val include = new munit.Tag("org.podval.tools.test.ExcludedTest")
val exclude = new munit.Tag("org.podval.tools.test.ExcludedTest")
test("excluded".tag(include).tag(exclude)) {}

When tagging classes used for inclusion/exclusion are not available, MUnit crashes with a ClassNotFound.

ScalaCheck

  • test filtering functionality is not available

  • test tagging: not supported, but if it is used via another test framework - like ScalaTest or specs2 - test tagging mechanisms provided by that framework can be used

  • assumptions: not supported

  • ignoring a test: not supported;

Nested Suites

In ScalaCheck, nesting is accomplished by using org.scalacheck.Properties.include():

object ScalaCheckNesting extends org.scalacheck.Properties("ScalaCheckNesting") {
  include(ScalaCheckNested)
}

object ScalaCheckNested extends org.scalacheck.Properties("ScalaCheckNested") {
  property("success") = org.scalacheck.Prop.passed
  property("failure") = org.scalacheck.Prop.falsified
}

With ScalaCheck, nested test cases are attributed to the nesting suite - and there is nothing that can be done about it, since ScalaCheck itself does not keep information about which class a property belongs to.

Scalaprops

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test: Property.forAll { …​ }.ignore("…​");

ScalaTest

  • test filtering: works fine

  • assumptions: not supported

  • ignoring a test: ignore should "be ignored";

Test Tagging

Tag tests with objects that extend org.scalatest.Tag:

object Include extends org.scalatest.Tag("org.podval.tools.test.IncludedTest")
object Exclude extends org.scalatest.Tag("org.podval.tools.test.ExcludedTest")
"excluded" should "not run" taggedAs(Include, Exclude) in {  true shouldBe false }

Nested Suites

In ScalaTest, nesting of the test suites is indicated by deriving the nesting class from org.scalatest.Suites and listing the nested suites in its constructor:

class ScalaTestNesting extends org.scalatest.Suites(
  new ScalaTestNested
)

Specs2

  • test filtering: works fine

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test: not supported;

Test Tagging

Tag tests with tag names:

exclude tests tagged for exclusion $excludedTest ${tag(
  "org.podval.tools.test.IncludedTest",
  "org.podval.tools.test.ExcludedTest"
)}

uTest

  • test filtering: does not support test case selectors and runs all test cases in the class.

  • test tagging: not supported

  • assumptions: not supported

  • ignoring a test: not supported;

Nested Suites

Only test suites defined in the same test class can be nested:

import utest._

object UTestNesting extends TestSuite {
  val tests: Tests = Tests {
    test("UTestNesting") {
      test("UTestNested") {
        test("success") { assert(1 == 1) }
        test("failure") { assert(1 == 0) }
      }
    }
  }
}

Weaver Test

  • test filtering: does not support test case selectors and runs all test cases in the class

  • test tagging: not supported

  • nested suites: not supported

  • assumptions: not supported

  • ignoring a test: not supported;

ZIO Test

  • test filtering: treats specific test case inclusions as wildcards, and instead of running just the named test cases runs all whose names contain the specified string, because the only test case name-based filtering that ZIO Test supports is "search terms", which work as wildcards

  • ignoring a test: test("ignored") { …​ } @@ zio.test.TestAspect.ignore

  • assumption: test("assumption") { …​ } @@ zio.test.TestAspect.ifProp("property")(string ⇒ false)

Test Tagging

Tag tests with tag names using TestAspect.tag:

test("tagged") { ... } @@ TestAspect.tag(
  "org.podval.tools.test.IncludedTest",
  "org.podval.tools.test.ExcludedTest"
)

Nested Suites

import zio.test._

object ZIOTestNesting extends ZIOSpecDefault {
  override def spec: Spec[TestEnvironment, Any] = suite("ZIOTestNesting")(
    ZIOTestNested.spec
  )
}
object ZIOTestNested extends ZIOSpecDefault {
  override def spec: Spec[TestEnvironment, Any] = suite("ZIOTestNested")(
    test("success") { assertTrue(1 == 1) },
    test("failure") { assertTrue(1 == 0) },
  )
}

Further reading

How the plugin is put together, and the history of that work, is in Implementation notes.

Releases

Used by

Contributors

Languages