In this next step we’ll add ScalaTest and some unit tests to our Mill project to see how that works.
Mill’s directory structure
Because Mill’s directories are different than other build tools like Maven and SBT, the first thing to do is to create a directory structure for our tests. The way to do this is to create a test directory under the HelloWorld directory, at the same level as the src directory. To create the entire structure we need with one command, including a directory for our hello package, use this command:
mkdir -p HelloWorld/test/src/test/hello
I’ll also create the test file in that directory to be clear about what I’m doing:
touch HelloWorld/test/src/test/hello/HelloTests.scala
In a few moments I’ll add some tests to that HelloTests.scala file.
Here’s what the directory structure looks like now:
$ tree HelloWorld
HelloWorld
├── src
│ └── main
│ ├── resources
│ └── scala
│ └── hello
│ └── Hello.scala
└── test
└── src
└── test
└── hello
└── HelloTests.scala
To reiterate the point, the test directory and the src directory are at the same level in the hierarchy, both directly underneath the HelloWorld directory.
Updating MIll’s build.sc file
With that test directory structure in place, the next thing to do is to update our build.sc file to (a) add the ScalaTest dependencies and also (b) reflect that test directory. Here’s the updated file:
import mill._, scalalib._
object HelloWorld extends ScalaModule {
def scalaVersion = "2.12.11"
object test extends Tests {
def ivyDeps = Agg(
ivy"org.scalactic::scalactic:3.1.1",
ivy"org.scalatest::scalatest:3.1.1"
)
def testFrameworks = Seq("org.scalatest.tools.Framework")
}
}
Notice a few things:
- I create the
test
object inside theHelloWorld
object - Dependencies are added with the
ivyDeps
parameter - Mill uses an
ivy
string interpolator (similar to other interpolators likes
,raw
,f
) and the syntax shown for Scala resources - I haven’t looked into the
Agg
class yet, but it looks like aSeq
, so that’s good enough for now
A thing I appreciate at this point is that the build.sc file is just plain Scala code. I’m not a huge fan of most DSLs, so that’s a nice win.
Writing the tests
To create the tests, first edit the Hello.scala file so we have something to test. Here are the updated contents of that file:
package hello
object Hello extends App {
println(Constants.hello)
}
object Constants {
val hello = "Hello, world"
}
Now lets edit our ScalaTest test file. As a reminder, this is its name and path:
HelloWorld/test/src/test/hello/HelloTests.scala
This is a normal ScalaTest file, so put these contents in that file:
package hello
import org.scalatest.funsuite.AnyFunSuite
class HelloSuite extends AnyFunSuite {
test("Test that ‘Hello’ string is correct") {
assert(Constants.hello == "Hello, world")
}
test ("Another test ...") (pending)
}
Running the tests
To run the tests, use this Mill command:
mill HelloWorld.test
Its output looks like this:
Compiling MillTest1/build.sc
[50/56] HelloWorld.test.compile
[info] Compiling 1 Scala source to MillTest1/out/HelloWorld/test/compile/dest/classes ...
[info] Done compiling.
[56/56] HelloWorld.test.test
HelloSuite:
- Test that ‘Hello’ string is correct
- Another test ... (pending)
Cool, the tests work.
If you want to continually run the tests while you’re working, use Mill’s “watch” option:
$ mill --watch HelloWorld.test
[56/56] HelloWorld.test.test
HelloSuite:
- Test that ‘Hello’ string is correct
- Another test ... (pending)
Watching for changes to 2 dirs and 4 files... (Ctrl-C to exit)
Cool, ScalaTest seems to work pretty easily with Mill.