1. Introduction
ScalaTest is a domain-specific language that provides us with the necessary tools to test our code. It provides the development team with a framework for completing the software test life cycle.
We already have a good idea of why testing our code is essential – to catch bugs. However, there might be some benefits of using a tool like ScalaTest that we may not be conversant with.
In this tutorial, we’ll see how to use ScalaTest to check the validity of particular code arrangements.
2. Why Are We Testing?
First, let’s appreciate some of the features that ScalaTest provides. We’ll also look at why we need to carry out tests and what might occur if we don’t. The most obvious reason would be to identify potential errors.
However, some more subtle reasons might and should compel us to test our Scala code:
- ScalaTest provides insightful reasons for why certain code didn’t run. For example, whether it was because of a type mismatch or a failed condition
- In case we are creating a library, it is necessary to ensure that it is fault resistant. We can check for these errors early on before releasing the library
- During the Continuous Integration and Continuous Delivery (CI/CD) process, the risk of pushing bugs into deployment is more likely. Given the time constraints, it might be difficult to check each new functionality manually. However, with tools like ScalaTest and sbt, we can reduce manual work
3. Check If a Snippet Compiles or Not
There are two ways in which we can do this. We can either use Scala’s Matchers or Assertions.
First, we need to ensure that we have the ScalaTest dependency in our project. We can download it from the Maven repository using the following dependency in our pom.xml file:
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_3</artifactId>
<version>3.2.16</version>
<scope>test</scope>
</dependency>
To avoid compilation errors, we may also need to check that our Scala version is compatible with the ScalaTest version. For the code here, we used Scala version 3.2.2.
3.1. Using Matchers
Now, we need to import the packages for our desired testing style for working with our project. We’ll use the FlatSpec style here. We can refer to Scala’s official documentation for more information about which style will benefit our team.
We will put our file (TestSpec.scala) in the src/test/scala directory. Now, we’ll extend the AnyFlatSpec class:
class TestSpec extends AnyFlatSpec {
// write tests here
}
Note that we have directly extended the style class for simplicity. In actual development, it would actually be better for us to define an abstract class that extends the Style class:
abstract class BaseSpec extends AnyFlatSpec with should.Matchers with OptionValues with Inside with Inspectors
In the implemented class (TestSpec), we can now describe our test cases. In particular, we are interested in checking whether some lines of code will run.
This is useful for several reasons. Sometimes, we need to ensure that some usage of our code library is not acceptable. Conversely, it is sometimes also necessary to ensure that a given arrangement of code will run.
We use the should.Matchers trait to achieve this. To check that some code does not compile, we can express this as below:
"val x: Int = 258" shouldNot compile
The above statement will check that the statement with that syntax won’t compile. We can run the test using the command below in the parent directory of our project:
sbt test
We can also use the testOnly command in the sbt REPL:
sbt:testproject> testOnly TestSpec
If unsuccessful, we should expect an output similar to the one below:
[info] *** 1 SUITE ABORTED ***
[error] Error during tests:
[error] TestSpec
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 5 s, completed Jul 24, 2023, 6:20:30 AM
One important thing to note is that we get a failed test here because of a syntax error. However, if we wanted to check if it doesn’t run because of a type error, then we would instead use:
"val x: Int = 2.0" shouldNot typeCheck
Running this test will give a TestFailedException, as shown in the output above. This exception provides us with information about which line of code actually failed instead of just dumping the entire stack trace for us to analyze.
Sometimes, we may need to ensure that a certain arrangement runs. In that case, we can use:
"val x: Int = 2" should compile
Sample output:
[info] SetSpec:
[info] Run completed in 184 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[success] Total time: 5 s, completed Jul 24, 2023, 6:06:32 AM
In the examples above, we have used the should.Matchers trait. However, if we wanted to use the must.Matchers trait, we would still be able to achieve the same goal for our tests. The only difference would be the choice of words.
The syntax is still the same, although we need to import the trait library:
"val x: String = 5" mustNot compile
"val x: Int = 2" must compile
"val x: Double = 2" mustNot typeCheck
3.2. Using Assertions
We can check if a given piece of code compiles or doesn’t because of a syntax or type error, just like with the should and must Matchers:
assertCompiles("val x: Int = 8" ) // test that snippet compiles
assertDoesNotCompile("val x: String = 5") // test that snippet does not compile
assertTypeError("val x: String = 5") // test that snippet does not compile because of a type error
The output messages for using Assertions are similar to those for using Matchers. It would also be helpful for us to use assertThrows to catch certain types of exceptions:
assertThrows[MyException] { // some event here }
4. Conclusion
In this article, we learned how to test whether a Scala snippet compiles using Matchers or Assertions.
The code is available over on GitHub.