Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to ignore test utility methods when scalatest detects failures?

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)

like image 902
fommil Avatar asked Jul 09 '16 18:07

fommil


People also ask

How do you ignore test cases in ScalaTest?

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.

How do you fail a ScalaTest?

Or, if you want the test to fail with a message, write: fail("I've got a bad feeling about this.")

Which testing framework have you used for Scala?

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.


1 Answers

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
like image 100
Bill Venners Avatar answered Oct 13 '22 19:10

Bill Venners