Search code examples
scalasbtscalatestrunnerscala-metals

Sbt run tests from src/main/scala instead of src/test/scala?


Say I have a scalatest class in main/scala, like

import org.scalatest.FunSuite

class q3 extends FunSuite {
    test("6 5 4 3 2 1") {
        val digits = Array(6,5,4,3,2,1)
        assert(digits.sorted === Array(1,2,3,4,5,6))
    }
}

How do I run it with sbt?

I've tried sbt test, sbt testOnly, sbt "testOnly *q3" and they all had output like

[info] Run completed in 44 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[info] No tests to run for Test / testOnly

A similar question from a few years back said they successfully used testOnly but I can't get it to work.

The metals extension on VSCode shows a "test" link when the file is open which successfully runs the test, but doesn't show how it does that. I want to know how to do it through sbt.


Solution

  • Put ScalaTest on Compile classpath in build.sbt like so

    libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.0"
    

    and then call org.scalatest.run runner explicitly from within an App, for example,

    object MainTests extends App {
      org.scalatest.run(new ExampleSpec)
    }
    

    Putting it together we have in src/main/scala/example/MainTests.scala

    package example
    
    import org.scalatest.matchers.should.Matchers
    import org.scalatest.flatspec.AnyFlatSpec
    import collection.mutable.Stack
    import org.scalatest._
    
    class ExampleSpec extends AnyFlatSpec with Matchers {
      "A Stack" should "pop values in last-in-first-out order" in {
        val stack = new Stack[Int]
        stack.push(1)
        stack.push(2)
        stack.pop() should be (2)
        stack.pop() should be (1)
      }
    }
    
    object MainTests extends App {
      org.scalatest.run(new ExampleSpec)
    }
    

    and run it with runMain example.MainTests. Furthermore, we could gather tests in Suites and execute all like so

    class ExampleASpec extends FlatSpec with Matchers {
      "ExampleA" should "run" in { succeed }
    }
    class ExampleBSpec extends FlatSpec with Matchers {
      "ExampleB" should "run" in { succeed }
    }
    
    class ExampleSuite extends Suites(
      new ExampleASpec,
      new ExampleBSpec,
    )
    
    object MainTests extends App {
      (new ExampleSuite).execute()
    }