Other goodies
ScalaTest has many other traits and classes that can help you address specific problems.
This page gives tutorials on just a few of them.
For the full list, see ScalaTest's Scaladoc documentation.
Here are links to the goodies described on this page:
Using Inside
ScalaTest's Inside
trait contains an inside
construct, which allows you to make statements about nested object graphs using pattern matching.
For example, given the following case classes:
case class Address(street: String, city: String, state: String, zip: String)
case class Name(first: String, middle: String, last: String)
case class Record(name: Name, address: Address, age: Int)
You could write:
inside (rec) { case Record(name, address, age) =>
inside (name) { case Name(first, middle, last) =>
first should be ("Sally")
middle should be ("Ann")
last should be ("Jones")
}
inside (address) { case Address(street, city, state, zip) =>
street should startWith ("25")
city should endWith ("Angeles")
state should equal ("CA")
zip should be ("12345")
}
age should be < 99
}
If an assertion fails, the error message will include the toString
of each value passed
to inside
clauses enclosing the failed assertion. For example, if rec
in
the previous expression was defined like this:
val rec = Record(
Name("Sally", "Anna", "Jones"),
Address("25 Main St", "Los Angeles", "CA", "12345"),
38
)
The error message will read:
"Ann[a]" was not equal to "Ann[]", inside Name(Sally,Anna,Jones),
inside Record(Name(Sally,Anna,Jones),Address(25 Main St,Los Angeles,CA,12345),38)
Using OptionValues
ScalaTest's OptionValues
trait provides an implicit conversion that adds a value
method
to Option
, which will return the value of the option if it is defined,
or throw TestFailedException
if not.
This construct allows you to express in one statement that an option should be defined
and that its value should meet some expectation. Here's an example:
opt.value should be > 9
Or, using an assertion instead of a matcher expression:
assert(opt.value > 9)
Were you to simply invoke get
on the Option
,
if the option wasn't defined, it would throw a NoSuchElementException
:
val opt: Option[Int] = None
opt.get should be > 9
The NoSuchElementException
would cause the test to fail, but without providing a stack depth pointing
to the failing line of test code. This stack depth, provided by TestFailedException
(and a
few other ScalaTest exceptions), makes it quicker for
users to navigate to the cause of the failure. Without OptionValues
, to get
a stack depth exception you would need to make two statements, like this:
val opt: Option[Int] = None
opt should be ('defined)
opt.get should be > 9
The OptionValues
trait allows you to state that more concisely:
val opt: Option[Int] = None
opt.value should be > 9
Using EitherValues
ScalaTest's EitherValues
trait provides an implicit conversion that adds left.value
and right.value
methods
to Either
, which will return the selected value of the Either
if defined,
or throw TestFailedException
if not.
This construct allows you to express in one statement that an Either
should be left or right
and that its value should meet some expectation. Here's are some examples:
either1.right.value should be > 9
either2.left.value should be ("Muchas problemas")
Or, using assertions instead of matcher expressions:
assert(either1.right.value > 9)
assert(either2.left.value === "Muchas problemas")
Were you to simply invoke right.get
or left.get
on the Either
,
if the Either
wasn't defined as expected (e.g., it was a Left
when you expected a Right
), it
would throw a NoSuchElementException
:
val either: Either[String, Int] = Left("Muchas problemas")
either.right.get should be > 9
The NoSuchElementException
would cause the test to fail, but without providing a stack depth pointing
to the failing line of test code. This stack depth, provided by TestFailedException
(and a
few other ScalaTest exceptions), makes it quicker for
users to navigate to the cause of the failure. Without EitherValues
, to get
a stack depth exception you would need to make two statements, like this:
val either: Either[String, Int] = Left("Muchas problemas")
either should be ('right)
either.right.get should be > 9
The EitherValues
trait allows you to state that more concisely:
val either: Either[String, Int] = Left("Muchas problemas")
either.right.value should be > 9
Using PartialFunctionValues
ScalaTest's PartialFunctionValues
trait provides an implicit conversion that adds a valueAt
method
to PartialFunction
, which will return the value (result) of the function applied to the argument passed to valueAt
,
or throw TestFailedException
if the partial function is not defined at the argument.
This construct allows you to express in one statement that a partial function should be defined for a particular input,
and that its result value should meet some expectation. Here's an example:
pf.valueAt("IV") should equal (4)
Or, using an assertion instead of a matcher expression:
assert(pf.valueAt("IV") === 4)
Were you to simply invoke apply
on the PartialFunction
, passing in an input value,
if the partial function wasn't defined at that input, it would throw some exception, but likely not one
that provides a stack depth:
val pf: PartialFunction[String, Int] = Map("I" -> 1, "II" -> 2, "III" -> 3, "IV" -> 4)
pf("V") should equal (5)
The NoSuchElementException
thrown in this situation would cause the test to fail, but without providing a stack depth pointing
to the failing line of test code. This stack depth, provided by TestFailedException
(and a
few other ScalaTest exceptions), makes it quicker for
users to navigate to the cause of the failure. Without PartialFunctionValues
, to get
a stack depth exception you would need to make two statements, like this:
val pf: PartialFunction[String, Int] = Map("I" -> 1, "II" -> 2, "III" -> 3, "IV" -> 4)
pf.isDefinedAt("V") should be (true)
pf("V") should equal (5)
The PartialFunctionValues
trait allows you to state that more concisely:
val pf: PartialFunction[String, Int] = Map("I" -> 1, "II" -> 2, "III" -> 3, "IV" -> 4)
pf.valueAt("V") should equal (5)
Using PrivateMethodTester
ScalaTest's PrivateMethodTester
trait facilitates the testing of private methods.
To test a private method, mix in trait PrivateMethodTester
and
create a PrivateMethod
object, like this:
val decorateToStringValue = PrivateMethod[String]('decorateToStringValue)
The type parameter on PrivateMethod
, in this case String
, is the result type of the private method
you wish to invoke. The symbol passed to the PrivateMethod.apply
factory method, in this
case 'decorateToStringValue
, is the name of the private method to invoke. To test
the private method, use the invokePrivate
operator, like this:
targetObject invokePrivate decorateToStringValue(1)
Here, targetObject
is a variable or singleton object name referring to the object whose
private method you want to test. You pass the arguments to the private method in the parentheses after
the PrivateMethod
object.
The result type of an invokePrivate
operation will be the type parameter of the PrivateMethod
object, thus you need not cast the result to use it. In other words, after creating a PrivateMethod
object, the
syntax to invoke the private method
looks like a regular method invocation, but with the dot (.
) replaced by invokePrivate
.
The private method is invoked dynamically via reflection, so if you have a typo in the method name symbol, specify the wrong result type,
or pass invalid parameters, the invokePrivate
operation will compile, but throw an exception at runtime.
One limitation to be aware of is that you can't use PrivateMethodTester
to test a private method
declared in a trait, because the class the trait gets mixed into will not declare that private method. Only the
class generated to hold method implementations for the trait will have that private method. If you want to
test a private method declared in a trait, and that method does not use any state of that trait, you can move
the private method to a companion object for the trait and test it using PrivateMethodTester
that
way. If the private trait method you want to test uses the trait's state, your best options are to test it
indirectly via a non-private trait method that calls the private method, or make the private method package access
and test it directly via regular static method invocations.
Using WrapWith
You can use ScalaTest's WrapWith
annotation to associate a wrapper suite with a non-Suite
class, so
it can be run via ScalaTest. The WrapWith
annotation is similar in spirit to JUnit's RunWith
annotation.
A class will be considered annotated with WrapWith
if it is annotated directly or one of its superclasses (but
not supertraits) are annotated with WrapWith
.
The wrapper suite must have a public, one-arg constructor that takes a Class
instance whose type parameter
is compatible with the class to wrap: i.e., the class being annotated with WrapWith
.
ScalaTest will load the class to wrap and construct a new instance of the wrapper suite, passing in the Class
instance for the class to wrap.
Here's an example:
@WrapWith(classOf[Specs1Runner])
class LegacySpec extends Specification {
// ...
}
The Specs1Runner
would need to have a public, no-arg constructor that accepts subclasses of Specification
:
class Specs1Runner(clazz: Class[_ <: Specification]) {
// ...
}
Next, learn about ScalaTest's philosophy and design.