Hi Jonathan,
Good question. Both BeforeAndAfter and BeforeAndAfterEach are stackable traits that override the runTest lifecycle method. They are stackable because they call super.runTest as part of their runTest implementation. So you can stack them. The order is therefore determined by the order of linearization.
The good thing about that is the behavior is very well specified. The bad thing is that linearization can be confusing to Scala programmers. The easy way to think about it is the trait you mix in last will have its runTest invoked first, and that means it will be run farthest from the test. (I.e., the leftmost trait will be the first before to happen and the last after to happen.)
I think easiest to show with an example. Here's an example in which you have two User traits, User1 and User2, which extend BeforeAndAfterEach, and an ExampleSpec class that mixes in User1 and User2 and also BeforeAndAfter:
import org.scalatest.Suite
import org.scalatest.FunSpec
import org.scalatest.BeforeAndAfterEach
import collection.mutable.ListBuffer
import org.scalatest.BeforeAndAfter
trait User1 extends BeforeAndAfterEach { this: Suite =>
override def beforeEach() {
println("User1 beforeEach start")
super.beforeEach() // To be stackable, must call super.beforeEach
println("User1 beforeEach end")
}
override def afterEach() {
println("User1 afterEach start")
try {
super.afterEach() // To be stackable, must call super.afterEach
}
finally {
println("User1 afterEach end")
}
}
}
trait User2 extends BeforeAndAfterEach { this: Suite =>
override def beforeEach() {
println("User2 beforeEach start")
super.beforeEach() // To be stackable, must call super.beforeEach
println("User2 beforeEach end")
}
override def afterEach() {
println("User2 afterEach start")
try {
super.afterEach() // To be stackable, must call super.afterEach
}
finally {
println("User2 afterEach end")
}
}
}
class ExampleSpec extends FunSpec with User1 with User2 with BeforeAndAfter {
before {
println("ExampleSpec's before clause")
}
after {
println("ExampleSpec's after clause")
}
describe("Traits in Scala") {
it("can be stacked") {
println("IN TEST: Traits in Scala can be stacked")
}
it("can be understood, hopefully") {
println("IN TEST: Traits in Scala can be understood, hopefully")
}
}
}
For this " with User1 with User2 with BeforeAndAfter " ordering, you get this output (note the BeforeAndAfter clauses are executed outermost):
Mi-Novia:stacking bv$ scala -cp ../fresh18/target/jar_contents/ org.scalatest.run ExampleSpec
Run starting. Expected test count is: 2
ExampleSpec:
ExampleSpec's before clause
User2 beforeEach start
User1 beforeEach start
User1 beforeEach end
User2 beforeEach end
Traits in Scala
IN TEST: Traits in Scala can be stacked
- can be stacked
User2 afterEach start
User1 afterEach start
User1 afterEach end
User2 afterEach end
ExampleSpec's after clause
ExampleSpec's before clause
User2 beforeEach start
User1 beforeEach start
User1 beforeEach end
User2 beforeEach end
IN TEST: Traits in Scala can be understood, hopefully
User2 afterEach start
User1 afterEach start
User1 afterEach end
User2 afterEach end
ExampleSpec's after clause
- can be understood, hopefully
Run completed in 80 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 2, failed 0, ignored 0, pending 0
All tests passed.
Now consider this class that mixes the traits in a different order "with BeforeAndAfter with User1 with User2":
class ExampleSpec2 extends FunSpec with BeforeAndAfter with User1 with User2 {
before {
println("ExampleSpec's before clause")
}
after {
println("ExampleSpec's after clause")
}
describe("Traits in Scala") {
it("can be stacked") {
println("IN TEST: Traits in Scala can be stacked")
}
it("can be understood, hopefully") {
println("IN TEST: Traits in Scala can be understood, hopefully")
}
}
}
Now the User2 trait, which extends in BeforeAndAfterEach is rightmost, so BeforeAndAfterEach's runTest method will get to go first.
Mi-Novia:stacking bv$ scala -cp ../fresh18/target/jar_contents/ org.scalatest.run ExampleSpec2
Run starting. Expected test count is: 2
ExampleSpec2:
User2 beforeEach start
User1 beforeEach start
User1 beforeEach end
User2 beforeEach end
ExampleSpec's before clause
Traits in Scala
IN TEST: Traits in Scala can be stacked
- can be stacked
ExampleSpec's after clause
User2 afterEach start
User1 afterEach start
User1 afterEach end
User2 afterEach end
User2 beforeEach start
User1 beforeEach start
User1 beforeEach end
User2 beforeEach end
ExampleSpec's before clause
IN TEST: Traits in Scala can be understood, hopefully
ExampleSpec's after clause
User2 afterEach start
User1 afterEach start
User1 afterEach end
User2 afterEach end
- can be understood, hopefully
Run completed in 83 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 2, failed 0, ignored 0, pending 0
All tests passed.
You can copy the code and try it yourself in different orders.
By the way the one thing you *can't* stack is multiple traits that extend BeforeAndAfter. You won't be able to mix two of those together into the same class. The reason I disallowed that is precisely because the order of execution wouldn't be obvious. BeforeAndAfterEach has methods that call super versions of themselves, so the ordering is defined by the rules of Scala.
Thanks.
Bill
--
Bill Venners
Artima, Inc.
http://www.artima.com