I have this convenience method in my tests:
def assertFormat[T: SexpFormat](start: T, expect: Sexp): Unit = {
val sexp = start.toSexp
assert(sexp === expect, s"${sexp.compactPrint} was not ${expect.compactPrint}")
expect.convertTo[T] should be(start)
}
which is basically a convenience for running an assertion pattern that I do a lot.
It's not possible to rewrite this as a Matcher
because of the implicit requirement on SexpFormat[T]
(although I'd be interested in hearing of ways to do this that don't require me to write the type MyFormat
in foo should roundTrip[MyFormat](...)
)
If any tests fail inside this utility method, scalatest will flag the internals of assertFormat
as being the cause of the test failure. But I really want scalatest to detect the caller of this method to be the cause of the test. How can I do that?
i.e. current output is
[info] - should support custom missing value rules *** FAILED ***
[info] SexpNil did not equal SexpCons(SexpSymbol(:duck),SexpCons(SexpNil,SexpNil)) nil was not (:duck nil) (FormatSpec.scala:11)
[info] org.scalatest.exceptions.TestFailedException:
[info] at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:529)
[info] at org.scalatest.FlatSpec.newAssertionFailedException(FlatSpec.scala:1691)
[info] at org.scalatest.Assertions$AssertionsHelper.macroAssert(Assertions.scala:502)
[info] at org.ensime.sexp.formats.FormatSpec$class.assertFormat(FormatSpec.scala:11)
[info] at org.ensime.sexp.formats.test.FamilyFormatsSpec.assertFormat(FamilyFormatsSpec.scala:151)
[info] at org.ensime.sexp.formats.test.FamilyFormatsSpec.roundtrip(FamilyFormatsSpec.scala:156)
[info] at org.ensime.sexp.formats.test.FamilyFormatsSpec$$anonfun$12.apply(FamilyFormatsSpec.scala:222)
[info] at org.ensime.sexp.formats.test.FamilyFormatsSpec$$anonfun$12.apply(FamilyFormatsSpec.scala:221)
FormatSpec.scala:11
is where my assertFormat
is defined. The real failure is in FamilyFormatsSpec.scala:222
(which is calling another convenience method FamilyFormatsSpec.scala:156
)
It has been inserted into Scaladoc by pretending it is a trait. When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the SetSpec in the above example with the @Ignore tag annotation means that both tests in the class will be ignored.
Or, if you want the test to fail with a message, write: fail("I've got a bad feeling about this.")
There are two outstanding Scala testing frameworks: ScalaTest and ScalaCheck. Both are easy to use, but there are a few differences depending on the test approach you want to use. This specification for Rooms indicates that the property that rules if the room is available has to check for all possible rooms.
This is possible in ScalaTest 3.0 by taking an implicit org.scalactic.source.Position
in your custom assertion. The position will then be computed (via a macro) whenever your assertFormat
method is called, and that position will be picked up by the assert and matcher expression inside assertFormat
. Here is how it would look:
import org.scalactic.source
def assertFormat[T: SexpFormat](start: T, expect: Sexp)(implicit pos: source.Position): Unit = {
val sexp = start.toSexp
assert(sexp === expect, s"${sexp.compactPrint} was not ${expect.compactPrint}")
expect.convertTo[T] should be(start)
}
The following example illstrates it. If you have ScalaTest 3.0 on the class path, just :load the following file into the Scala REPL:
:paste
import org.scalatest._
import org.scalactic._
import Matchers._
case class Sexp(o: Any) {
def compactPrint: String = o.toString
def convertTo[T: SexpFormat]: Sexp = implicitly[SexpFormat[T]].convertIt(o)
override def toString = "I'm too sexp for my shirt."
}
trait SexpFormat[T] {
def convertIt(o: Any): Sexp = new Sexp(o)
}
implicit class Sexpify(o: Any) {
def toSexp: Sexp = new Sexp(o)
}
implicit def universalSexpFormat[T]: SexpFormat[T] = new SexpFormat[T] {}
def assertFormat[T: SexpFormat](start: T, expect: Sexp): Unit = {
val sexp = start.toSexp
assert(sexp === expect, s"${sexp.compactPrint} was not ${expect.compactPrint}")
expect.convertTo[T] should be(start)
}
import org.scalatest.exceptions.TestFailedException
val before = intercept[TestFailedException] { assertFormat(1, new Sexp) }
println(s"${before.failedCodeStackDepth} - This stack depth points to the assert call inside assertFormat")
import org.scalactic.source
def betterAssertFormat[T: SexpFormat](start: T, expect: Sexp)(implicit pos: source.Position): Unit = {
val sexp = start.toSexp
assert(sexp === expect, s"${sexp.compactPrint} was not ${expect.compactPrint}")
expect.convertTo[T] should be(start)
}
val after = intercept[TestFailedException] { betterAssertFormat(1, new Sexp) }
println(s"${after.failedCodeStackDepth} - This stack depth is the betterAssertFormat call itself in your test code")
It will print:
3 - This stack depth points to the assert call inside assertFormat
4 - This stack depth is the betterAssertFormat call itself in your test code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With