Test configuration classes and metadata types for organizing and controlling test execution. This includes test options, tags for categorizing tests, and source location tracking for error reporting.
Configuration class for customizing individual test behavior including naming, tagging, and execution control.
/**
* Configuration options for individual tests
* @param name The test name
* @param tags Set of tags applied to the test
* @param location Source code location of the test
*/
final class TestOptions(val name: String, val tags: Set[Tag], val location: Location) extends Serializable {
/** Create a new TestOptions with a different name */
def withName(newName: String): TestOptions
/** Create a new TestOptions with different tags */
def withTags(newTags: Set[Tag]): TestOptions
/** Create a new TestOptions with a different location */
def withLocation(newLocation: Location): TestOptions
/** Add a single tag to this test */
def tag(t: Tag): TestOptions
// Convenience methods for common tags
/** Mark this test as expected to fail */
def fail: TestOptions
/** Mark this test as potentially flaky */
def flaky: TestOptions
/** Skip this test during execution */
def ignore: TestOptions
/** Mark this test as pending implementation */
def pending: TestOptions
/** Mark this test as pending with a comment */
def pending(comment: String): TestOptions
/** Run only this test (skip all others) */
def only: TestOptions
}
/**
* Companion object with factory methods
*/
object TestOptions extends TestOptionsConversions {
def apply(name: String)(implicit loc: Location): TestOptions
}
/**
* Implicit conversions for convenience
*/
trait TestOptionsConversions {
/** Convert a string to TestOptions with current location */
implicit def testOptionsFromString(name: String)(implicit loc: Location): TestOptions
}Usage Examples:
class ConfigurationExamples extends FunSuite {
test("basic test") {
assert(true)
}
test("slow integration test".tag(Slow)) {
// This test is marked as slow
performIntegrationTest()
}
test("flaky network test".flaky) {
// This test may fail intermittently
callExternalAPI()
}
test("unimplemented feature".pending) {
// This test is not yet implemented
}
test("work in progress".pending("waiting for API changes")) {
// Pending with a specific reason
}
test("debug this specific test".only) {
// Only this test will run when debugging
assert(complexCondition())
}
// Combining multiple tags
test(TestOptions("comprehensive test")
.tag(Slow)
.tag(new Tag("integration"))) {
performComprehensiveTest()
}
}Metadata class representing a complete test case with all its configuration and execution details.
/**
* Complete test case with name, body, tags, and location
* @param name The test name
* @param body Function that executes the test and returns a Future
* @param tags Set of tags applied to this test
* @param location Source code location where test is defined
*/
final class Test(val name: String, val body: () => Future[Any], val tags: Set[Tag], val location: Location) extends Serializable {
/** Create a new test with a different name */
def withName(newName: String): Test
/** Create a new test with a different body */
def withBody(newBody: () => Future[Any]): Test
/** Create a new test with a transformed body */
def withBodyMap(newBody: Future[Any] => Future[Any]): Test
/** Create a new test with different tags */
def withTags(newTags: Set[Tag]): Test
/** Add a single tag to this test */
def tag(newTag: Tag): Test
/** Create a new test with a different location */
def withLocation(newLocation: Location): Test
/** Get annotations for this test (includes tags and location) */
def annotations: Array[Annotation]
}Tags for categorizing and controlling test execution behavior.
/**
* Base tag class for categorizing tests
* @param value The tag identifier string
*/
class Tag(val value: String) extends munit.internal.junitinterface.Tag with Annotation with SerializableBuilt-in Tags:
// Pre-defined tags available in the munit package object
val Ignore = new Tag("Ignore") // Skip this test
val Only = new Tag("Only") // Run only this test
val Flaky = new Tag("Flaky") // Test may fail intermittently
val Fail = new Tag("Fail") // Test is expected to fail
val Pending: Tag with PendingTag = new Tag("Pending") with PendingTag // Test is pending implementation
val Slow = new Tag("Slow") // Test is slow running
/**
* Pending tag with custom comment
* @param value The comment explaining why test is pending
*/
case class PendingComment(override val value: String) extends Tag(value) with PendingCommentTagUsage Examples:
class TagExamples extends FunSuite {
test("regular test") {
assert(true)
}
test("skip this test".tag(Ignore)) {
// This test will be skipped
fail("Should not run")
}
test("database test".tag(Slow).tag(new Tag("database"))) {
// Multiple tags can be applied
connectToDatabase()
}
// Custom tags
val Integration = new Tag("Integration")
val External = new Tag("External")
test("API integration".tag(Integration).tag(External)) {
callExternalAPI()
}
// Environment-specific tags
test("linux only test".tag(new Tag("linux"))) {
assume(System.getProperty("os.name").toLowerCase.contains("linux"))
// Test linux-specific functionality
}
}Source code location tracking for precise error reporting and IDE integration.
/**
* Source code location information
* @param path The file path where the test is defined
* @param line The line number in the file
*/
final class Location(val path: String, val line: Int) extends Annotation with Serializable {
/** Extract just the filename from the full path */
def filename: String
/** Format location as "path:line" */
override def toString: String
}
/**
* Companion object with utility methods
*/
object Location extends MacroCompat.LocationMacro {
/** Empty location placeholder for tests created programmatically */
def empty: Location
}Usage Examples:
class LocationExamples extends FunSuite {
test("location is automatically captured") {
// Location is captured via macro at compile time
assert(true)
}
// Manual test creation with explicit location
val manualTest = new Test(
"manual test",
() => Future.successful(assert(true)),
Set.empty,
new Location("MyTest.scala", 42)
)
}MUnit respects several environment variables for controlling test execution:
MUNIT_FLAKY_OK: Set to allow flaky tests to pass even if they failCI: Detected automatically to adjust behavior in continuous integrationTags can be used with build tools and IDEs to filter which tests run:
// Run only slow tests
sbt 'testOnly -- --include-tags=Slow'
// Exclude flaky tests
sbt 'testOnly -- --exclude-tags=Flaky'
// Run tests with multiple tags
sbt 'testOnly -- --include-tags=Integration,Database'Create domain-specific tags for your project:
object TestTags {
val Unit = new Tag("Unit")
val Integration = new Tag("Integration")
val Performance = new Tag("Performance")
val Security = new Tag("Security")
val UI = new Tag("UI")
val API = new Tag("API")
}
class ServiceTests extends FunSuite {
import TestTags._
test("unit test".tag(Unit)) {
// Fast unit test
}
test("integration test".tag(Integration).tag(Slow)) {
// Slower integration test
}
test("performance benchmark".tag(Performance).tag(Slow)) {
// Performance testing
}
}Type-class that enables type-safe comparisons in MUnit assertions with custom failure handling.
/**
* Type-class for comparing values in MUnit assertions
* Enables type-safe comparisons between related types
*/
trait Compare[A, B] {
/** Check if two values are equal according to comparison rules */
def isEqual(obtained: A, expected: B): Boolean
/** Handle comparison failures with custom error messages and diff output */
def failEqualsComparison(obtained: A, expected: B, title: Any, assertions: Assertions)(implicit loc: Location, options: DiffOptions): Nothing
}
/**
* Companion object providing default comparison implementations
*/
object Compare extends ComparePriority1 {
/** Default comparison using == for any two types */
def defaultCompare[A, B]: Compare[A, B]
}
/**
* Implicit priority for subtype comparisons
*/
trait ComparePriority1 extends ComparePriority2 {
/** Allow comparison when A is a subtype of B */
implicit def compareSubtypeWithSupertype[A, B](implicit ev: A <:< B): Compare[A, B]
}
/**
* Implicit priority for supertype comparisons
*/
trait ComparePriority2 {
/** Allow comparison when B is a subtype of A */
implicit def compareSupertypeWithSubtype[A, B](implicit ev: A <:< B): Compare[B, A]
}Usage Examples:
// Custom comparison for domain objects
implicit val userCompare: Compare[User, User] = new Compare[User, User] {
def isEqual(obtained: User, expected: User): Boolean =
obtained.id == expected.id && obtained.email == expected.email
def failEqualsComparison(obtained: User, expected: User, title: Any, assertions: Assertions)(implicit loc: Location, options: DiffOptions): Nothing =
assertions.failComparison(s"Users not equal: $title", obtained, expected)
}
class UserTests extends FunSuite {
test("user equality") {
val user1 = User(1, "alice@example.com", "Alice")
val user2 = User(1, "alice@example.com", "Alice Smith") // Different name
assertEquals(user1, user2) // Uses custom comparison (ignores name)
}
}Interface for customizing how values are displayed in test failure messages.
/**
* Interface for objects that can customize their printed representation
*/
trait Printable {
/** Append custom representation to a StringBuilder */
def print(out: StringBuilder, indent: Int): Unit
}Usage Examples:
case class ComplexData(values: Map[String, Any]) extends Printable {
def print(out: StringBuilder, indent: Int): Unit = {
out.append("ComplexData(\n")
values.foreach { case (key, value) =>
out.append(" " * (indent + 1))
out.append(s"$key = $value\n")
}
out.append(" " * indent)
out.append(")")
}
}
class PrintableTests extends FunSuite {
test("complex data comparison") {
val data1 = ComplexData(Map("x" -> 1, "y" -> 2))
val data2 = ComplexData(Map("x" -> 1, "y" -> 3))
// Custom printing will be used in failure message
assertEquals(data1, data2)
}
}JUnit runner that enables MUnit integration with IDEs and build tools.
/**
* JUnit runner for MUnit test suites
* Automatically applied to Suite classes via @RunWith annotation
*/
class MUnitRunner(val cls: Class[_ <: Suite], newInstance: () => Suite) extends Runner
/**
* Companion object with runner utilities
*/
object MUnitRunner {
/** Create a runner for a suite class */
def apply(cls: Class[_ <: Suite]): MUnitRunner
}Usage:
// Automatically applied to all Suite subclasses
@RunWith(classOf[MUnitRunner])
abstract class Suite extends PlatformSuite {
// Suite implementation
}