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 === 1Example with the description “where example 2
must be true” and the code 2 === 2All 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