Is there some preferred way to design a Specs2 test, with lots of tests that depend on the results of previous tests?
Below, you'll find my current test suite. I don't like the var
s inbetween the test fragments. They're "needed" though, since some tests generate ID numbers that subsequent tests reuses.
Should I perhaps store the ID numbers in a Specs2 Context instead, or create a separate Object that holds all mutable state? And place only test fragments in the specification object? Or is there some even better approach?
If a test fails, I'd like to cancel the remaining test at the same depth. Can I make the test fragments depend upon each other? (I know I can cancel remaining matchers in a single test fragment (by using mutable tests, or via orSkip), but what about cancelling whole fragments?)
.
object DatabaseSpec extends Specification {
sequential
"The Data Access Object" should {
var someId = "" // These var:s feels error prone, is there a better way?
"save an object" >> {
someId = database.save(something)
someId must_!= ""
// I'd like to cancel the remaining tests, below, at this "depth",
// if this test fragmen fails. Can I do that?
// (That is, cancel "load one object", "list all objects", etc, below.)
}
"load one object" >> {
anObject = database.load(someId)
anObject.id must_== someId
}
"list all objects" >> {
objs = database.listAll()
objs.find(_.id == someId) must beSome
}
var anotherId = ""
...more tests that create another object, and
...use both `someId` and `anotherId`...
var aThirdId = ""
...tests that use `someId`, `anotherId` and `aThirdId...
}
"The Data Access Object can also" >> {
...more tests...
}
}
There are 2 parts to your question: using vars for storing intermediary state, and stopping examples when one is failing.
1 - Using vars
There are some alternatives to using vars when using a mutable specification.
You can use
lazy vals
representing the steps of your process:Since those values are lazy, they will only be called when the examples are executed.
If you're ready to change the structure of your current specification you can also use the latest 1.12.3-SNAPSHOT and group all those small expectations into one example:
If any of those expectations fail then the rest will not be executed and you will get a failure message like:
Another possibility, which would require 2 small improvements, would be to use the Given/When/Then way of writing specifications but using "thrown" expectations inside
Given
andWhen
steps. As you can see in the User Guide, theGiven/When/Then
steps extract data from strings and pass typed information to the nextGiven/When/Then
:The improvements, which I'm going to do, are:
groupAs(".*") and
when there's nothing to extract from the string description.In that case it should be enough to write:
Another possibility would be to allow to directly write:
where a
Given[T]
object can be created from aString => MatchResult[T]
because theMatchResult[T]
object already contains a value of typeT
, that would become a "Given".2 - Stop the execution after a failing example
Using the implicit
WhenFail
Around
context is certainly the best way to do what you want (unless you go with the expectations descriptions as shown above the G/W/T example).Note on
step(stepOnFail = true)
The
step(stepOnFail = true)
works by interrupting the following examples if one example in the previous block of concurrent examples failed. However, when you're usingsequential
, that previous block is limited to just one example. Hence what you're seeing. Actually I think that this is a bug and that all the remaining examples should not be executed, whether you're using sequential or not. So stay tuned for a fix coming up this week-end.Specs doc states that you may use .orSkip to skip the rest of the example in case of a failure
But I have not tried it personally
(Concerning question 1: I don't know if there's some better alternative to the
var
s inside the examples. Perhaps my examples are simply too long, and perhaps I should split my Spec:s into many smaller specs.)Concerning question 2, I found in this email by etorreborre that stopping subsequent tests can be done like so:
(Ex4 will be skipped if ex1, ex2 or ex3 fails. (This doesn't work as expected in Specs2 < 1.12.3 if you're using a sequential spec, however.))
Here's another way: According to this Specs2 Googl groups email by etorreborre one can have subsequent tests stop on failure, like so: ("example2" would be skipped, but "example3" and "4" would run)
In this email by etorreborre there's a method to cancel subsequent specifications if an example fails, if you've include a list of specifications:
And you'd need to edit test options in
build.sbt
so the child specs aren't executed again indepentendly after they've been included. From the email: