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 (see spock.util.environment.OperatingSystem)

  • jvm Information about the JVM (see spock.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