In a Specification you generally want to include 2 things:
With
you can create an Acceptance specification where all the informal text is written in one place and the code is written somewhere else. The name “acceptance” comes from the fact that it might be easier for a non-developer to just read some text to validate your specification
you can create a Unit specification where the code is interleaved with the text. The name “unit” comes from the fact that unit specifications have a structure which is close to unit tests in classical frameworks such as JUnit
Both ways of writing specifications have advantages and drawbacks:
Acceptance specifications are easier to read as a narrative but require navigation between the text and the code. You also need to define an is
method holding the body of the specification
Unit specifications are easier to navigate but the text tends to be lost in a sea of code
An acceptance specification extends org.specs2.Specification
and defines the is
method. You can implement this method with an interpolated s2
string:
class MySpecification extends org.specs2.Specification:
def is = s2"""
this is my specification
where example 1 must be true $e1
where example 2 must be true $e2
"""
def e1 = 1 === 1
def e2 = 2 === 2
The s2
string contains the text of your specification as well as some references to methods (e1
and e2
) defining some code eventually evaluating to a Result
(this can take many forms, from a simple Boolean, to a Future[Result]
, or some value with an AsExecution
instance).
When the Specification is executed, the s2
string is analysed and 2 Examples
are created then executed:
Example
with the description “where example 1 must be true” and the code 1 === 1
Example
with the description “where example 2 must be true” and the code 2 === 2
All the rest, "this is my specification"
, is parsed as Text
and is not executed.
A unit specification extends org.specs2.mutable.Specification
and uses the >>
operator to create “blocks” containing Texts
and Examples
:
class MySpecification extends org.specs2.mutable.Specification:
"this is my specification" >> {
"where example 1 must be true" >> {
1 must ===(1)
}
"where example 2 must be true" >> {
2 must ===(2)
}
}
This specification creates one piece of Text
and 2 Examples
as before but:
is
method (this means that a mutable variable is used to collect the Texts
and Examples
hence the mutable
package name)However once a specification is created with all its Texts
and Examples
, the execution will be the same, whether it is an Acceptance one or a Unit one.
The >>
blocks can be nested and this allows you to structure your specification so that the outermost blocks describe a general context while the innermost ones describe more specific contexts. A similar effect can be achieved by simply indenting text in an acceptance specification.
There is another major difference between the acceptance specifications and unit specifications. The first style encourages you to write one expectation per example while the second allows to use several. One expectation per example is useful because when a specification fails, you know immediately what is wrong. However it is sometimes expensive to setup data for an example. In that case, having several expectations sharing the same setup might be preferable.
The good news is that for each of the 2 main styles, acceptance and unit, you can choose exactly which “Expectation mode” you prefer if the default mode is not convenient.
In an acceptance specification, by default, the Result
of an Example
is always given by the last statement of its body. For instance, this example will never fail because the first expectation is lost:
// this will never fail!
s2"""
my example on strings $e1
"""
def e1 =
// because this expectation will not be returned,...
"hello" must haveSize(10000)
"hello" must startWith("hell")
If you want to get both expectations you will need to use and
between them:
s2"""
my example on strings $e1
"""
def e1 =
("hello" must haveSize(10000)) and
("hello" must startWith("hell"))
This is a bit tedious and not very pleasing to read so you can see why this mode encourages one expectation per example only! If you want to declare several expectations per example, you can mix-in the org.specs2.matcher.ThrownExpectations
trait to the specification.
With a unit specification you get “thrown expectations” by default. When an expectation fails, it throws an exception and the rest of the example is not executed:
class MySpecification extends org.specs2.mutable.Specification:
"This is my example" >> {
1 === 2 // this fails
1 === 1 // this is not executed
}
It is also possible to use the “functional” expectation mode with a unit specification by mixing in the org.specs2.matcher.NoThrownExpectations
trait.
Example