7

The specs2 Matchers guide states:

throwA[ExceptionType](message = "boom") additionally checks if the exception message is as expected

But when I use this, the message is apparently matched on the entire stacktrace instead of only the exception message.

Test

"cont'd what if -- running test again shows that the app has already died" in {
  running(new FakeApplication(additionalConfiguration = inLocalPostgresDatabase())) {
    db2 withSession {
      val comboboxOpsClass = new ComboboxOps(database)
    }
  } must throwA[SQLException](message = "Attempting to obtain a connection from a pool that has already been shutdown")
}

Stacktrace

[error]      'Attempting to obtain a connection from a pool that has already been shutdown. 
[error]      Stack trace of location where pool was shutdown follows:
[error]       java.lang.Thread.getStackTrace(Thread.java:1568)
[error]       com.jolbox.bonecp.BoneCP.captureStackTrace(BoneCP.java:572)
[error]       com.jolbox.bonecp.BoneCP.shutdown(BoneCP.java:161)

many many more lines

[error]       org.specs2.execute.ResultExecution$class.execute(ResultExecution.scala:22)
[error]       org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:116)
[error]       org.specs2.specification.FragmentExecution$class.executeBody(FragmentExecution.scala:28)
[error]       
[error]       sbt.ForkMain$Run.runTestSafe(ForkMain.java:211)
[error]       sbt.ForkMain$Run.runTests(ForkMain.java:187)
[error]       sbt.ForkMain$Run.run(ForkMain.java:251)
[error]       sbt.ForkMain.main(ForkMain.java:97)
[error]      ' doesn't match '.*Attempting to obtain a connection from a pool that has already been shutdown.*' (FakeApplicationSpec.scala:138)

Could somebody point me to a working example of this usage of specs2?

Meredith
  • 2,617
  • 3
  • 26
  • 55

3 Answers3

7

I hate to say it but I never found a generic way of discovering what the text was other than to either let it happen and add it after the fact... or go to the source of the exception and copy it from there. Here's what I did in Novus-JDBC, line 91:

"handle when take more than it can give" in{
  val iter0 = nonCounter()

  val iter = iter0 slice (0,10)

  (iter next () must be greaterThan 0) and
  (iter next () must be greaterThan 0) and
  (iter next () must be equalTo -1) and
  (iter next () must be equalTo 3) and
    (iter.hasNext must beFalse) and
    (iter next() must throwA(new NoSuchElementException("next on empty iterator")))
}
wheaties
  • 34,173
  • 12
  • 82
  • 126
  • 1
    I think I just spotted a bug in my unit test by putting this up here. Should be `iter` and `iter0` in the first two `next () must be greaterThan 0` matches. – wheaties Jan 09 '14 at 21:35
1

You'd have the option of matching against an exception message if you implement the exception as a case class and include that message in its first constructor parameter list.

case class ObjectionException(statement: String)
extends    Exception(statement)

try throw ObjectionException("I object")
catch {
  case ObjectionException("I object")   => println("I objected to myself?")
  case ObjectionException("You object") => println("I don't care!")
  case ObjectionException(objection)    => println(s"Objection: $objection")
}

// Exiting paste mode, now interpreting.

I objected to myself?
defined class ObjectionException

scala>

You could also use a Regex to match the message:

val ContentObjection = ".*Content.*".r

try throw ObjectionException("ObjectionableContent")
catch {
  case ObjectionException(ContentObjection()) => println("Questionable Content")
  case ObjectionException(objection)          => println(s"Objection: $objection")
}

// Exiting paste mode, now interpreting.

Questionable Content
ContentObjection: scala.util.matching.Regex = .*Content.*
Randall Schulz
  • 25,823
  • 4
  • 58
  • 81
0

The framework is throwing the stack trace "captured" as the string message, instead of just stuffing the exception somewhere and throwing it later.

The test rig is looking for s".*$message.*".r, which won't work without DOTALL mode (which lets the "dot" match newlines, aka single-line mode):

scala> val text = """abc
     | def
     | ghi
     | jkl"""
text: String =
abc
def
ghi
jkl

scala> val r = ".*def.*".r
r: scala.util.matching.Regex = .*def.*

scala> import PartialFunction._
import PartialFunction._

scala> cond(text) { case r() => true }
res0: Boolean = false

scala> val r2 = "(?s).*def.*".r
r2: scala.util.matching.Regex = (?s).*def.*

scala> cond(text) { case r2() => true }
res1: Boolean = true

If you're testing for just a prefix of the text, you can do that by appending the inline DOTALL (?s), i.e., message = "boom(?s)":

scala> val r3 = ".*abc(?s).*".r
r3: scala.util.matching.Regex = .*abc(?s).*

scala> cond(text) { case r3() => true }
res2: Boolean = true

But trying to test for anything on a subsequent line will fail:

scala> val r4 = ".*(?s)def.*".r
r4: scala.util.matching.Regex = .*(?s)def.*

scala> cond(text) { case r4() => true }
res3: Boolean = false

Anchors aweigh would work, if the test framework did that:

scala> val r5 = ".*def.*".r.unanchored
r5: scala.util.matching.UnanchoredRegex = .*def.*

scala> cond(text) { case r5() => true }
res4: Boolean = true
som-snytt
  • 38,672
  • 2
  • 41
  • 120