
- Scala - Home
- Scala - Overview
- Scala - Features
- Scala - Environment Setup
- Scala - Build Tool (SBT)
- Scala - REPL
- Scala - Dot & Dotty
- Scala - Basic Syntax
- Scala - Hello World Program
- Scala - Identifiers
- Scala - Keywords
- Scala - Comments
- Scala - Code Blocks
- Scala - Semicolon
- Scala - Constructs
- Scala - Expressions
- Scala - Input and Output
- Scala - Optional Braces
- Scala - Underscore (_)
- Data Types and Variables
- Scala - Data Types
- Scala - Type Bounds
- Scala - Context Bound
- Scala - Variances
- Scala - Type Hierarchy
- Scala - Variables
- Scala - Variable Scopes
- Scala - Literals
- Scala - Numeric Types
- Scala - Boolean Types
- Scala - Char Type
- Scala - Unit Types
- Scala - Strings
- Scala - Arrays
- Scala - Null Type
- Scala - Nothing
- Scala - Any Type
- Scala - AnyRef Type
- Scala - Unified Types
- Scala - Dates and Times
- Scala - Ranges
- Scala - Multidimensional Arrays
- Scala - WrappedArray
- Scala - StringBuilder
- Scala - String Interpolation
- Scala - StringContext
- Scala - Type Casting
- Scala var vs val
- Scala Operators
- Scala - Operators
- Scala - Rules for Operators
- Scala - Arithmetic Operators
- Scala - Relational Operators
- Scala - Logical Operators
- Scala - Bitwise Operators
- Scala - Assignment Operators
- Scala - Operators Precedence
- Scala - Symbolic Operators
- Scala - Range Operator
- Scala - String Concatenation Operator
- Scala Conditional Statements
- Scala - IF ELSE
- Scala - IF-ELSE-IF-ELSE Statement
- Scala - Nested IF-ELSE Statement
- Scala Loop Statements
- Scala - Loop Statements
- Scala - while Loop
- Scala - do-while Loop
- Scala - Nested Loops
- Scala - for Loop
- Scala - break Statement
- Scala - yield Keyword
- Scala Classes & Objects
- Scala - Classes & Objects
- Scala - Constructors
- Scala - Auxiliary Constructor
- Scala - Primary Constructor
- Scala - This Keyword
- Scala - Nested Classes
- Scala - Getters and Setters
- Scala - Object Private Fields
- Scala - Singleton Object
- Scala - Companion Objects
- Scala - Creating Executable Programs
- Scala - Stateful Object
- Scala - Enumerations
- Scala - Polymorphism
- Scala - Access Modifiers
- Scala - Apply Method
- Scala - Update Methods
- Scala - UnapplySeq Method
- Scala - Inheritance
- Scala - Extending a Class
- Scala - Method Overloading
- Scala - Method Overriding
- Scala - Generic Classes
- Scala - Generic Functions
- Scala - Superclass Construction
- Scala Methods & Functions
- Scala - Methods
- Scala - Functions
- Scala - Methods vs Functions
- Scala - Main Methods
- Scala - Functions Call-by-Name
- Scala - Functions with Named Arguments
- Scala - Function with Variable Arguments
- Scala - Recursion Functions
- Scala - Default Parameter Values
- Scala - Functions without Parameters
- Scala - Implicit Parameters
- Scala - Higher-Order Functions
- Scala - Nested Functions
- Scala - Extension Methods
- Scala - Anonymous Functions
- Partially Applied Functions
- Scala - Lazy Val
- Scala - Pure Function
- Scala - Currying Functions
- Scala - Control Abstractions
- Scala - Corecursion
- Scala - Unfold
- Scala - Tail Recursion
- Scala - Infinite Sequences
- Scala - Dynamic Invocation
- Scala - Lambda Expressions
- Scala - Polymorphic Functions
- Scala Collections
- Scala - Collections
- Mutable and Immutable Collections
- Scala - Lists
- Scala - Sets
- Scala - Maps
- Scala - TreeMap
- Scala - SortedMap
- Scala - Tuples
- Scala - Iterators
- Scala - Options
- Scala - NumericRange
- Scala - Infinite Streams
- Scala - Parallel Collections
- Scala Advanced Types
- Scala - Union Types
- Scala - Intersection Types
- Scala - Type Aliases
- Scala - Structural Types
- Scala - Match Expression
- Scala - Singleton Type Operator
- Scala - Abstract Types
- Scala - Dependent Types
- Scala - Abstract Type Bounds
- Scala - Higher-Kinded Types
- Scala - Opaque Type Alias
- Scala - Path-Dependent Types
- Scala - Type Lambdas
- Scala - Type Inference
- Scala - Algebraic Data Types
- Scala Pattern Matching
- Scala - Pattern Matching
- Scala - Guards
- Scala - Variables in Patterns
- Scala - Type Patterns
- Scala - The Matchable Trait
- Scala - Matching Arrays
- Scala - Matching Lists
- Scala - Matching Tuples
- Scala - Exception Handling
- Scala - Extractors
- Scala - Pattern Bindings
- Scala - Regular Expressions
- Scala - Case Classes
- Scala - Partial Functions
- Scala - Packaging and Imports
- Scala - Implicit Imports
- Scala - Export Clauses
- Scala - Nested Packages
- Scala - Chained Packages
- Scala - Package Objects
- Scala Files I/O
- Scala - Files I/O
- Scala - Writing Files
- Scala - Listing Files
- Scala - Deleting Directories
- Scala - Check File Exists
- Scala Advanced Concepts
- Scala - Closures
- Scala - Futures
- Scala - Promises
- Scala - Traits
- Scala - Trait Mixins
- Scala - Layered Traits
- Scala - Trait Linearization
- Scala - Sealed Traits
- Scala - Transparent Traits
- Scala - Process Management
- Scala - Scaladoc
- Scala - Literal Type Arithmetic
- Scala - Inline keyword
- Scala - Def, Var & Val
- Scala - Dropped Features
- Scala Unit Testing
- Scala - Unit Testing
- Scala - uTest
- Scala - MUnit
- Scala - ScalaTest Runner
- Scala - ScalaMock
- Scala - JUnit
- Scala - Mocking
- Scala - BDD Testing
Scala - Write and Run Unit Tests Using MUnit
Unit testing is an important practice in software development. Unit testing is used to ensure code quality and to catch bugs early. MUnit is a lightweight and efficient testing library for Scala. MUnit is used to streamline the process of writing tests. It builds on top of JUnit. So it is compatible with existing tools and IDE integrations, like IntelliJ and VS Code. MUnit provides clear and actionable error reports to identify and fix issues.
Why MUnit?
There are various advantages of MUnit -
- It is a JUnit-based testing style.
- No additional Scala dependencies - So you can use it cross-building across different Scala versions.
- Better diffs on assertion failures - So you can identify and understand test failures easily.
- Cross-platform compatibility - MUnit compiles to JVM bytecode, JavaScript via Scala.js. MUnit is optimized binaries via Scala Native/LLVM.
- Actionable errors - Test reports are color-coded and clear of test failures.
Setting Up
You need to add the following dependencies to your build.sbt to get started with MUnit using sbt -
libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test
You also need to add for sbt versions prior to 1.5.0 -
testFrameworks += new TestFramework("munit.Framework")
Writing Tests
Test suite in MUnit is a Scala class. It extends munit.FunSuite. Each test is defined using the test() method.
Example
You should have this dependency in your build.sbt file to execute below example -
import Dependencies._ ThisBuild / scalaVersion := "2.13.14" lazy val root = (project in file(".")) .settings( name := "Demo", version := "0.1", libraryDependencies ++= Seq( "org.scalameta" %% "munit" % "0.7.29" % Test ) )
Now, you can define MyTest.scala under src/test/scala folder -
class MyTests extends munit.FunSuite { test("sum of two integers") { val obtained = 2 + 2 val expected = 4 assertEquals(obtained, expected) } }
Commands
Now, you execute these commands to compile and run test -
sbt compile sbt test
Output
MyTests: + sum of two integers 0.039s [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 4 s, completed 08-Aug-2024, 10:04:38Îûam
Assertions
MUnit supports various assertion methods, like assert(), assertEquals(), assertNotEquals(), assertNoDiff(), etc.
Code | Description |
---|---|
assert() | Fails the test if the given boolean condition is false. |
assertEquals() | Checks if two values are equal, providing readable diffs for failures. |
assertNotEquals() | Ensures two values are not equal. |
assertNoDiff() | Compares strings while ignoring spaces and newlines. |
intercept() | Verifies that a block of code throws a specific exception. |
Example
Consider this example for above same build.sbt file -
class MyEvenNumbersTests extends munit.FunSuite { test("all even numbers") { val input: List[Int] = List(1, 2, 3, 4) val obtainedResults: List[Int] = input.map(_ * 2) assert(obtainedResults.forall(_ % 2 == 0)) } }
Running Tests
You can run all your test suites using sbt with the following commands -
Run all tests -
sbt test
The output will be -
MyEvenNumbersTests: + all even numbers 0.032s [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 3 s, completed 08-Aug-2024, 10:10:58Îûam
Run a single test suite -
sbt "testOnly example.MyTests"
The output will be -
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] No tests to run for Test / testOnly [success] Total time: 0 s, completed 08-Aug-2024, 10:11:40Îûam
You need to append only to the test name to run a single test within a suite -
class MathSuite extends munit.FunSuite { test("addition") { assert(1 + 1 == 2) } test("multiplication".only) { assert(3 * 7 == 21) } }
Running Tests
You can run all your test suites using sbt with the following commands -
Run all tests -
sbt test
The output will be -
MathSuite: + multiplication 0.058s [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 3 s, completed 08-Aug-2024, 10:13:22Îûam
Run a single test suite -
sbt "testOnly example.MyTests"
The output will be -
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] No tests to run for Test / testOnly [success] Total time: 0 s, completed 08-Aug-2024, 10:14:50Îûam
Advanced Features
Async Tests:
MUnit supports asynchronous tests using Future.
import scala.concurrent.ExecutionContext.Implicits.global test("async test") { Future { assertEquals(2 + 2, 4) } }
Customizing Timeouts
The default timeout for async tests is 30 seconds, but you can customize it.
override val munitTimeout = Duration(1, "s")
Fixtures
You can manage resources efficiently using functional fixtures.
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!") } }
Composing Fixtures
You can use FunFixture.map2 to combine multiple fixtures.
val using2TempFiles: FunFixture[(os.Path, os.Path)] = FunFixture.map2(usingTempFile, usingTempFile) using2TempFiles.test("merge two files") { case (file1, file2) => // body of the test }
Intercepting Exceptions
You can verify that a block of code throws a specific exception.
import java.nio.file.NoSuchFileException class FileTests extends munit.FunSuite { test("read missing file") { val missingFile = os.pwd / "missing.txt" intercept[NoSuchFileException] { os.read(missingFile) } } }
Adding Clues
You can use clue for better error reports.
assert(clue(List(a).head) > clue(b))
Environment-Specific Tests
You can use assume for conditional test execution.
import scala.util.Properties test("home directory") { assume(Properties.isLinux, "this test runs only on Linux") assert(os.home.toString.startsWith("/home/")) }
Tagging Flaky Tests
You can mark tests as flaky using .flaky.
test("requests".flaky) { // I/O heavy tests that sometimes fail }
Declaring Tests Inside Helper Functions
You can reuse shared parts in test methods.
def check[T](name: String, original: List[T], expected: Option[T])(implicit loc: munit.Location): Unit = { test(name) { val obtained = original.headOption assertEquals(obtained, expected) } } check("basic", List(1, 2), Some(1)) check("empty", List(), Some(1)) check("null", List(null, 2), Some(null))
Declaring Tests Expected to Fail
You can mark tests expected to fail with .fail.
test("issue-456".fail) { // Reproduce reported bug }
Customizing Test Evaluation
You can extend munitTestTransforms to modify test evaluation.
case class Rerun(count: Int) extends munit.Tag("Rerun") class MyRerunSuite extends munit.FunSuite { override def munitTestTransforms = super.munitTestTransforms ++ List( new TestTransform("Rerun", { test => val rerunCount = test.tags.collectFirst { case Rerun(n) => n }.getOrElse(1) if (rerunCount == 1) test else { test.withBody(() => Future.sequence(1.to(rerunCount).map(_ => test.body()).toList)) } }) ) test("files".tag(Rerun(3))) { println("Hello") // will run 3 times } test("files") { // will run once, like normal } }
Customizing Test Names
You can also modify munitNewTest to customize test names dynamically.
class ScalaVersionSuite extends munit.FunSuite { val scalaVersion = scala.util.Properties.versionNumberString override def munitTestTransforms = super.munitTestTransforms ++ List( new TestTransform("append Scala version", { test => test.withName(test.name + "-" + scalaVersion) }) ) test("foo") { assert(!scalaVersion.startsWith("2.11")) } }