Scala - JUnit Testing



JUnit is a testing framework for Java applications. It is used to write and run repeatable automated tests for correctness of your code. You can use JUnit for testing Scala applications. There are various features and integration capabilities within Scala projects.

You can write unit tests and integration tests using JUnit. You can also test performance to validate the behavior and performance of your application.

JUnit 4 with Scala

Dependencies

You need to add the following dependency to your build.sbt file to use JUnit 4 in a Scala project -

 libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % "test" 

Now, you can run JUnit tests from SBT.

Writing and Running Tests

Consider this example for a simple JUnit 4 test in Scala -

 package com.example.scala import org.junit.Test import org.junit.Assert._ class IntJunitTests { @Test def testOneIsPositive(): Unit = { assertTrue(1 > 0) } @Test def testMinusOneIsNegative(): Unit = { assertTrue(-1 < 0) } } 

You can use the test command to run the tests from the sbt shell -

 sbt test 

The output will be,

 [info] Passed: Total 2, Failed 0, Errors 0, Passed 2 [success] Total time: 3 s, completed 08-Aug-2024, 11:45:48 am 

Advanced Test Execution

You can also run specific tests using commands like testOnly -

 sbt "testOnly com.example.scala.IntJunitTests" 

The output will be,

 [info] Passed: Total 2, Failed 0, Errors 0, Passed 2 [success] Total time: 0 s, completed 08-Aug-2024, 11:46:47 am 

You can run a single test within a class -

 sbt "testOnly -- *.IntJunitTests.testOneIsPositive" 

The output will be,

 [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 0 s, completed 08-Aug-2024, 11:47:29 am 

Test Suites

JUnit also supports test suites. You can group multiple test classes. For example,

 package com.example.scala import org.junit.runner.RunWith import org.junit.runners.Suite @RunWith(classOf[Suite]) @Suite.SuiteClasses(Array(classOf[IntJunitTests], classOf[StringJunitTests])) class TypesTestSuite 

You can run the test suite -

 sbt "testOnly *.TypesTestSuite" 

Custom Test Runner

Sometimes, you may need to customize the test runner to handle specific test execution requirements. You can create custom test runner, like this -

 import org.junit.runner.RunWith import org.scalatestplus.junit.JUnitRunner @RunWith(classOf[JUnitRunner]) class CustomTestRunner extends JUnitSuite { // Custom test logic here } 

JUnit 5 with Scala

Dependencies and Setup

JUnit 5 requires a different setup compared to JUnit 4. You need to add the following to your plugins.sbt -

 resolvers += Resolver.jcenterRepo addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.3") 

Now, you should have this following dependency in your build.sbt -

 libraryDependencies += "net.aichler" % "jupiter-interface" % "0.8.3" % Test 

Writing JUnit 5 Tests

Consider this example of using JUnit 5 in Scala -

 package com.example.scala import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class ExampleTests { @Disabled("Not implemented yet") @Test def testNotImplemented(): Unit = {} } 

Now, you can run the tests using -

 sbt test 

The output will be,

 [success] Total time: 3 s, completed 08-Aug-2024, 11:52:25 am 

Test Lifecycle Management

JUnit 5 provides annotations for managing the lifecycle of tests, like, @BeforeAll, @AfterAll, @BeforeEach, and @AfterEach. Here is how you can use these in Scala -

 package com.example.scala import org.junit.jupiter.api.{BeforeAll, AfterAll, BeforeEach, AfterEach, TestInstance, Test} import org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS) class LifecycleTests { @BeforeAll def beforeAll(): Unit = { println("Before all tests") } @AfterAll def afterAll(): Unit = { println("After all tests") } @BeforeEach def beforeEach(): Unit = { println("Before each test") } @AfterEach def afterEach(): Unit = { println("After each test") } @Test def testExample(): Unit = { println("Executing test") } } 

Note that you should have these dependencies in your build.sbt file -

 scalaVersion := "2.13.14" libraryDependencies ++= Seq( "org.junit.jupiter" % "junit-jupiter-api" % "5.8.1" % "test", "org.junit.jupiter" % "junit-jupiter-engine" % "5.8.1" % "test", "org.junit.platform" % "junit-platform-launcher" % "1.8.1" % "test" ) testFrameworks += new TestFramework("munit.Framework") 

Now, you can compile and run test using these commands -

 sbt clean compile sbt run 

The output will be,

 [success] Total time: 3 s, completed 08-Aug-2024, 12:00:04 pm 

ScalaTest Integration

Using ScalaTest with JUnit

ScalaTest provides more readable and concise assertions. You can use ScalaTest assertions with JUnit, mix in AssertionsForJUnit. For example,

 package com.example.scala import org.scalatestplus.junit.AssertionsForJUnit import org.junit.Assert._ import org.junit.Test class ExampleSuite extends AssertionsForJUnit { @Test def verifyEasy(): Unit = { assertEquals("ScalaTest is easy!", "ScalaTest is easy!") } @Test def verifyFun(): Unit = { assert("ScalaTest is fun!" == "ScalaTest is fun!") } } 

You need to add the following dependencies to your build.sbt -

 import Dependencies._ ThisBuild / scalaVersion := "2.13.14" lazy val root = (project in file(".")) .settings( name := "Demo", version := "0.1", libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.2.12" % "test", "com.github.sbt" % "junit-interface" % "0.13.3" % "test", "org.scalatestplus" %% "junit-4-12" % "3.2.2.0" % "test" ) ) 

The output will be,

 [info] Passed: Total 2, Failed 0, Errors 0, Passed 2 [success] Total time: 1 s, completed 08-Aug-2024, 2:14:31 pm 

Combining ScalaTest and JUnit

You can write tests that can be run by both JUnit and ScalaTest. For example,

 import org.scalatestplus.junit.JUnitSuite import org.junit.Assert._ import org.junit.Test import org.junit.Before class CombinedSuite extends JUnitSuite { var sb: StringBuilder = _ var lb: ListBuffer[String] = _ @Before def initialize(): Unit = { sb = new StringBuilder("ScalaTest is ") lb = new ListBuffer[String] } @Test def verifyEasy(): Unit = { sb.append("easy!") assertEquals("ScalaTest is easy!", sb.toString) assertTrue(lb.isEmpty) lb += "sweet" try { "verbose".charAt(-1) fail() } catch { case e: StringIndexOutOfBoundsException => // Expected } } @Test def verifyFun(): Unit = { sb.append("fun!") assert(sb.toString === "ScalaTest is fun!") assert(lb.isEmpty) lb += "sweeter" intercept[StringIndexOutOfBoundsException] { "concise".charAt(-1) } } } 

So, there can be flexibility in using either test framework.

Example: Testing a Calculator

Consider this test a simple calculator application -

 package com.example.scala import org.junit.Test import org.junit.Assert._ class CalculatorTests { class Calculator { def add(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a - b def multiply(a: Int, b: Int): Int = a * b def divide(a: Int, b: Int): Int = if (b != 0) a / b else throw new ArithmeticException("Division by zero") } val calculator = new Calculator @Test def testAddition(): Unit = { assertEquals(5, calculator.add(2, 3)) } @Test def testSubtraction(): Unit = { assertEquals(1, calculator.subtract(3, 2)) } @Test def testMultiplication(): Unit = { assertEquals(6, calculator.multiply(2, 3)) } @Test(expected = classOf[ArithmeticException]) def testDivisionByZero(): Unit = { calculator.divide(1, 0) } @Test def testDivision(): Unit = { assertEquals(2, calculator.divide(6, 3)) } } 

Example: Testing a Simple Scala Class

Consider this example of testing a simple Scala class -

 package com.example.scala import org.junit.Test import org.junit.Assert._ class Person(val name: String, val age: Int) class PersonTests { @Test def testPersonCreation(): Unit = { val person = new Person("John Doe", 30) assertEquals("John Doe", person.name) assertEquals(30, person.age) } @Test def testPersonAge(): Unit = { val person = new Person("Jane Doe", 25) assertTrue(person.age > 20) } } 
Advertisements