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("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".
Care should be taken when ignoring feature methods in a spec class annotated with spock.lang.Stepwise
since
later feature methods may depend on earlier feature methods having executed.
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.
Care should be taken when ignoring feature methods in a spec class annotated with spock.lang.Stepwise
since
later feature methods may depend on earlier feature methods having executed.
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"() { ... }
Care should be taken when ignoring feature methods in a spec class annotated with spock.lang.Stepwise
since
later feature methods may depend on earlier feature methods having executed.
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.
PendingFeature
To indicate that the feature is not fully implemented yet and should not be reported as error, annotate it with spock.lang.PendingFeature
.
The use case is to annotate tests that can not yet run but should already be committed.
The main difference to Ignore
is that the test are executed, but test failures are ignored.
If the test passes without an error, then it will be reported as failure since the PendingFeature
annotation should be removed.
This way the tests will become part of the normal tests instead of being ignored forever.
Groovy has the groovy.transform.NotYetImplemented
annotation which is similar but behaves a differently.
-
it will mark failing tests as passed
-
if at least one iteration of a data-driven test passes it will be reported as error
PendingFeature
:
-
it will mark failing tests as skipped
-
if at least one iteration of a data-driven test fails it will be reported as skipped
-
if every iteration of a data-driven test passes it will be reported as error
@PendingFeature
def "not implemented yet"() { ... }
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.
Stepwise
does not override the behaviour of annotations such as Ignore
, IgnoreRest
, and IgnoreIf
, so care
should be taken when ignoring feature methods in spec classes annotated with Stepwise
.
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([String])
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.
Caution
|
Temporarily changing the meta classes is only safe when specs are run in a single thread per JVM. Even though many execution environments do limit themselves to one thread per JVM, keep in mind that Spock cannot enforce this. |
RestoreSystemProperties
Saves system properties before the annotated feature method (including any setup and cleanup methods) gets run, and restores them afterwards.
Applying this annotation to a spec class has the same effect as applying it to all its feature methods.
@RestoreSystemProperties
def "determines family based on os.name system property"() {
given:
System.setProperty('os.name', 'Windows 7')
expect:
OperatingSystem.current.family == OperatingSystem.Family.WINDOWS
}
Caution
|
Temporarily changing the values of system properties is only safe when specs are run in a single thread per JVM. Even though many execution environments do limit themselves to one thread per JVM, keep in mind that Spock cannot enforce this. |
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