Search code examples
scalatestingbddspecs2

In specs2, conditionally executing subtests on result returned from function that may throw exceptions


In specs2, what is the proper way to express a pattern of subtests that only execute if its "parent" test returned a result without throwing an exception?

I have a function maybeGiveMeAThing, and it can either return a Thing, or throw exceptions.

A call looks like this:

val thing: Thing = maybeGiveMeAThing("foo", "bar" "baz")

I want to test that with a certain set of inputs, that maybeGiveMeAThing successfully returns a Thing without throwing an exception, and using the Thing returned, do further tests to ensure that it is the correct Thing returned for the parameters given to maybeGiveMeAThing.

The way I have the tests currently set up, if the call to maybeGiveMeAThing throws an exception, the entire test suite gets aborted. This would be the logic I prefer:

  • If a Thing was returned successfully, proceed with a set of subtests that analyze the contents of thing
  • If maybeGiveMeAThing threw an exception (any exception), skip the subtests that analyze thing, but continue with the rest of the tests

My existing test code looks roughly like:

// ...
"with good parameters" in {
  var thing: Thing = null   

  "return a Thing without throwing an exception" in {
    thing = maybeGiveMeAThing("some", "good", "parameters", "etc.")
  } should not(throwA[Exception])


  "the Thing returned should contain a proper Foo" in {
       thing.foo mustBe "bar"

  }
    //... etc ...
}

// ...

}

...although this feels like it's way off from the right way to do it. What would be the proper way?
(I would like to avoid using vars if I can help it.)


Solution

  • One possibility is to use a condition as in @alexwriteshere answer:

    "parent test" in {
      val thing: Option[Thing] = maybeGiveMeAThing("foo", "bar" "baz")
    
      thing match {
        case Some(thing) =>
          "child test 1" in ok
          "child test 2" in ok
        case None =>
          "skipped tests" in skipped
      }
    }
    

    However you need to add an Example in the None case so that the block of the in method has an acceptable type.

    The big drawback with this approach though is that the specification is being executed while being defined. This means that:

    • if there's an exception with the maybeGiveMeAThing then the whole specification will blow up
    • if you choose to exclude the examples using the thing, you will still have it being built

    Another option is to use a Step saying that any previous failure will skip all the next examples:

    class MySpec extends mutable.Specification {
      "This is a test with a thing" >> {
        lazy val thing: Option[Thing] = maybeGiveMeAThing("foo", "bar" "baz")
    
        "the thing should be ok" >> {
          thing must beOk
        }
    
        step(stopOnFail = true)
        "other examples with thing" >> {
          "ex1" >> ok
          "ex2" >> ok
        }
      }
    }