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 (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"() { ... }

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