Spock comes with a powerful extension mechanism, which allows to hook into a spec’s lifecycle to enrich or alter its behavior. In this chapter, we will first learn about Spock’s built-in extensions, and then dive into writing custom extensions.
Built-In Extensions
Most of Spock’s built-in extensions are annotation-driven. In other words, they are triggered by annotating a spec class or method with a certain annotation. You can tell such an annotation by its @ExtensionAnnotation meta-annotation.
Ignore
To temporarily prevent a feature method from getting executed, annotate it with spock.lang.Ignore:
@Ignore
def "my feature"() { ... }
For documentation purposes, a reason can be provided:
@Ignore(reason = "TODO")
def "my feature"() { ... }
To ignore a whole specification, annotate its class:
@Ignore
class MySpec extends Specification { ... }
In most execution environments, ignored feature methods and specs will be reported as "skipped".
IgnoreRest
To ignore all but a (typically) small subset of methods, annotate the latter with spock.lang.IgnoreRest:
def "I'll be ignored"() { ... }
@IgnoreRest
def "I'll run"() { ... }
def "I'll also be ignored"() { ... }
@IgnoreRest is especially handy in execution environments that don’t provide an (easy) way to run a subset of methods.
IgnoreIf
To ignore a feature method under certain conditions, annotate it with spock.lang.IgnoreIf, followed by a predicate:
@IgnoreIf({ System.getProperty("os.name").contains("windows") })
def "I'll run everywhere but on Windows"() { ... }
To make predicates easier to read and write, the following properties are available inside the closure:
-
sys
A map of all system properties -
env
A map of all environment variables -
os
Information about the operating system (seespock.util.environment.OperatingSystem
) -
jvm
Information about the JVM (seespock.util.environment.Jvm
)
Using the os
property, the previous example can be rewritten as:
@IgnoreIf({ os.windows })
def "I'll run everywhere but on Windows"() { ... }
Requires
To execute a feature method under certain conditions, annotate it with spock.lang.Requires
,
followed by a predicate:
@Requires({ os.windows })
def "I'll only run on Windows"() { ... }
Requires
works exactly like IgnoreIf
, except that the predicate is inverted. In general, it is preferable
to state the conditions under which a method gets executed, rather than the conditions under which it gets ignored.
Stepwise
To execute features in the order that they are declared, annotate a spec class with spock.lang.Stepwise
:
@Stepwise
class RunInOrderSpec extends Specification {
def "I run first"() { ... }
def "I run second"() { ... }
}
Stepwise
only affects the class carrying the annotation; not sub or super classes. Features after the first
failure are skipped.
Timeout
To fail a feature method, fixture, or class that exceeds a given execution duration, use spock.lang.Timeout
,
followed by a duration, and optionally a time unit. The default time unit is seconds.
When applied to a feature method, the timeout is per execution of one iteration, excluding time spent in fixture methods:
@Timeout(5)
def "I fail if I run for more than five seconds"() { ... }
@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
def "I better be quick" { ... }
Applying Timeout
to a spec class has the same effect as applying it to each feature that is not already annotated
with Timeout
, excluding time spent in fixtures:
@Timeout(10)
class TimedSpec extends Specification {
def "I fail after ten seconds"() { ... }
def "Me too"() { ... }
@Timeout(value = 250, unit = MILLISECONDS)
def "I fail much faster"() { ... }
}
When applied to a fixture method, the timeout is per execution of the fixture method.
When a timeout is reported to the user, the stack trace shown reflects the execution stack of the test framework when the timeout was exceeded.
Use
To activate one or more Groovy categories within the scope of a feature method or spec, use spock.util.mop.Use
:
class ListExtensions {
static avg(List list) { list.sum() / list.size() }
}
class MySpec extends Specification {
@Use(listExtensions)
def "can use avg() method"() {
expect:
[1, 2, 3].avg() == 2
}
}
This can be useful for stubbing of dynamic methods, which are usually provided by the runtime environment (e.g. Grails). It has no effect when applied to a helper method. However, when applied to a spec class, it will also affect its helper methods.
ConfineMetaClassChanges
To confine meta class changes to the scope of a feature method or spec class, use spock.util.mop.ConfineMetaClassChanges
:
@Stepwise
class FooSpec extends Specification {
@ConfineMetaClassChanges
def "I run first"() {
when:
String.metaClass.someMethod = { delegate }
then:
String.metaClass.hasMetaMethod('someMethod')
}
def "I run second"() {
when:
"Foo".someMethod()
then:
thrown(MissingMethodException)
}
}
When applied to a spec class, the meta classes are restored to the state that they were in before setupSpec
was executed,
after cleanupSpec
is executed.
When applied to a feature method, the meta classes are restored to as they were after setup
was executed,
before cleanup
is executed.
AutoCleanup
Automatically clean up a field or property at the end of its lifetime by using spock.lang.AutoCleanup
.
By default, an object is cleaned up by invoking its parameterless close()
method. If some other
method should be called instead, override the annotation’s value
attribute:
// invoke foo.dispose()
@AutoCleanup("dispose")
def foo
If multiple fields or properties are annotated with AutoCleanup
, their objects are cleaned up sequentially, in reverse
field/property declaration order, starting from the most derived class class and walking up the inheritance chain.
If a cleanup operation fails with an exception, the exception is reported by default, and cleanup proceeds with the next
annotated object. To prevent cleanup exceptions from being reported, override the annotation’s quiet
attribute:
@AutoCleanup(quiet = true)
def ignoreMyExceptions
Title and Narrative
To attach a natural-language name to a spec, use spock.lang.Title
:
@Title("This is easy to read")
class ThisIsHarderToReadSpec extends Specification {
...
}
Similarly, to attach a natural-language description to a spec, use spock.lang.Narrative
:
@Narrative("""
As a user
I want foo
So that bar
""")
class GiveTheUserFooSpec() { ... }
Issue
To indicate that a feature or spec relates to one or more issues in an external tracking system, use spock.lang.Issue
:
@Issue("http://my.issues.org/FOO-1")
class MySpec {
@Issue("http://my.issues.org/FOO-2")
def "Foo should do bar"() { ... }
@Issue(["http://my.issues.org/FOO-3", "http://my.issues.org/FOO-4"])
def "I have two related issues"() { ... }
}
Subject
To indicate one or more subjects of a spec, use spock.lang.Subject
:
@Subject([Foo, Bar]) { ... }
Additionally, Subject
can be applied to fields and local variables:
@Subject
Foo myFoo
Subject
currently has only informational purposes.
TODO More to follow.
Writing Custom Extensions
TODO