Testland
Browse all skills & agents

spock-tests

Configures and runs Spock - Groovy-based JVM testing framework with given/when/then BDD blocks, where: data tables for parametrized tests, built-in mocking via Mock()/Stub()/Spy(), interaction-based testing (verify method calls in declarative DSL), implicit assertions in then: blocks. Use when working with Java/Kotlin codebases that benefit from Groovy DSL expressiveness, or maintaining existing Spock projects.

Install with skills.sh (any agent)

npx skills add testland/qa --skill spock-tests
View source

spock-tests

Overview

Per spockframework.org/spock/docs (opens in new window):

Spock is a Groovy-based JVM test framework. Distinguishing properties:

  • given/when/then BDD blocks with implicit semantic structure
  • where: data tables for parametrized tests with the cleanest syntax in any JVM framework
  • Built-in mocking (no Mockito needed): Mock(), Stub(), Spy()
  • Interaction-based testing: verify method-call sequences in the then: block declaratively

For Kotlin-only projects, kotest-tests covers similar BDD territory natively. Spock's Groovy syntax is the strongest argument; modern JVM projects often prefer Kotlin DSLs.

Step 1 - Install

build.gradle.kts:

plugins {
    id("groovy")   // Groovy plugin needed for Spock
}

dependencies {
    testImplementation("org.spockframework:spock-core:2.4-M5-groovy-4.0")
    testImplementation(platform("org.junit:junit-bom:5.11.0"))
    testImplementation("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()   // Spock 2 runs on JUnit Platform
}

Test files: src/test/groovy/**/*Spec.groovy.

Step 2 - First test

Per sp-docs (opens in new window) structure:

import spock.lang.Specification

class CalculatorSpec extends Specification {
    def "adds two numbers"() {
        given:
        def calc = new Calculator()

        when:
        def result = calc.add(1, 2)

        then:
        result == 3
    }
}

The then: block contains assertions. Each statement is implicitly a boolean assertion - failure shows the full expression value, not just true/false.

Step 3 - given/when/then blocks

Per sp-docs (opens in new window):

BlockPurpose
setup: / given:Test fixture setup
when:Action being tested
then:Assertions on the action's effect
expect:Combined when+then for simple cases
where:Data table for parametrized tests
cleanup:Per-test cleanup
and:Subdivider for any block
def "user is created with default role"() {
    expect:
    new User("alice").role == "user"
}

def "registration validates email"() {
    given:
    def service = new RegistrationService()

    when:
    service.register(email)

    then:
    InvalidEmailException ex = thrown()
    ex.message == "Invalid email format"

    where:
    email << ["", "no-at-sign", "@no-domain", "spaces in@email.com"]
}

Step 4 - Data tables (Spock-distinctive)

The where: data table is Spock's killer feature:

def "addition cases"() {
    expect:
    Calculator.add(a, b) == result

    where:
    a   | b   || result
    1   | 2   || 3
    0   | 0   || 0
    -1  | 1   || 0
    100 | 200 || 300
}

Cleaner than JUnit 5's @CsvSource for visual inspection. Each row runs as a separate test; failures don't stop subsequent rows.

Step 5 - Built-in mocking

def "user service calls repository"() {
    given:
    def repo = Mock(UserRepository)
    def service = new UserService(repo)

    when:
    service.create("alice@example.com")

    then:
    1 * repo.save(_)   // exactly 1 call to save() with any arg
}

def "service handles repo failure"() {
    given:
    def repo = Stub(UserRepository) {
        save(_) >> { throw new SQLException("connection lost") }
    }
    def service = new UserService(repo)

    when:
    service.create("alice@example.com")

    then:
    thrown(SQLException)
}

def "spy delegates but observes"() {
    given:
    def realRepo = new InMemoryRepository()
    def spy = Spy(realRepo)
    def service = new UserService(spy)

    when:
    service.create("alice@example.com")

    then:
    1 * spy.save(_) >> { call -> callRealMethod() }   // delegate to real impl
}

Mock vs Stub vs Spy:

  • Mock() - verify-able interactions; throws on uninstructed calls
  • Stub() - no verification; default-value response unless instructed
  • Spy() - wraps real object; observe calls + optionally override

Step 6 - Interaction cardinality

then:
1 * service.method()           // exactly once
0 * service.method()           // never
(1..3) * service.method()      // 1 to 3 times
(_) * service.method()         // any number (default)
1 * service.method(_)          // any args
1 * service.method("alice", _) // partial matchers
1 * service.method() >> 42     // returns 42 when called
1 * service.method() >>> [1, 2, 3]   // returns 1 first call, 2 second, etc.

Step 7 - Lifecycle hooks

def setupSpec() { /* once before all tests in spec */ }
def cleanupSpec() { /* once after */ }
def setup() { /* before each test */ }
def cleanup() { /* after each test */ }

Step 8 - CI integration

Same as JUnit (Spock runs on JUnit Platform):

- run: ./gradlew test jacocoTestReport
- uses: codecov/codecov-action@v4
  with: { files: build/reports/jacoco/test/jacocoTestReport.xml }

Anti-patterns

Anti-patternWhy it failsFix
Use Mockito alongside SpockTwo mocking APIs in one suiteUse Spock's built-in (Step 5)
expect: for multi-step setupMixes given + when + thenUse given/when/then explicitly
Skip 1 * cardinality, just _ *Loses interaction-count checkAlways specify (Step 6)
Use Spock for Java-only projectGroovy adds dependency + classpath weightUse JUnit 5 (Java-native)
Mock when Stub sufficesOver-strict; tests fail on incidental callsStub for non-verified deps (Step 5)

Limitations

  • Requires Groovy compiler in the build (extra build step + classpath weight).
  • Pure-Java projects gain little vs JUnit 5; Kotlin teams benefit more from Kotest.
  • Groovy syntax learning curve for non-Groovy teams.
  • Spock 2 requires Java 8+ (Spock 1 is end-of-life).

References

  • sp-docs (opens in new window) - Spock documentation
  • spockframework.org - landing
  • junit5-tests, kotest-tests, testng-tests, scalatest - sister tools
  • test-code-conventions

Related skills

assertj

Reference for AssertJ - the canonical JVM fluent-assertion library pairable with JUnit 5 / TestNG / Spock; covers the assertThat() entry point, collection matchers (contains, containsExactly, allSatisfy, extracting), exception assertions (assertThatThrownBy, catchThrowable), SoftAssertions for multi-failure collection, recursive comparison (usingRecursiveComparison) for deep equality, and domain-specific custom assertions via AbstractAssert. Use when writing JVM tests that need richer failure messages than built-in assertEquals, or when verifying complex object graphs, exception types, or collections.

junit5-tests

Configures and runs JUnit 5 (Jupiter) - modern JVM testing platform with annotations (`@Test` / `@ParameterizedTest` / `@RepeatedTest` / `@TestFactory`), lifecycle hooks (`@BeforeAll` / `@BeforeEach` / `@AfterEach` / `@AfterAll`), extension model (`@ExtendWith`), display names (`@DisplayName`), conditional execution (`@EnabledOnOs`, `@EnabledIf`), parallel execution config; integrates with Maven Surefire / Gradle test task / IntelliJ. Use when the user works with Java / Kotlin codebases needing the modern JVM standard.

kotest-tests

Configures and runs Kotest - Kotlin-native test framework with multiple specification styles (StringSpec, FunSpec, BehaviorSpec, DescribeSpec, ShouldSpec, FreeSpec, FeatureSpec, ExpectSpec, AnnotationSpec); rich matcher library; built-in property-based testing (alternative to jqwik); coroutines support; data-driven testing; isolation modes per-spec or per-test; integrates with Gradle JVM test task. Use when working with Kotlin and wanting Kotlin-idiomatic DSL over JUnit 5's annotation-driven approach. Matchers (shouldBe, shouldContain) are bundled with the runner and are not a drop-in replacement for a standalone JVM assertion library; for assertion-only use paired with JUnit 5 / TestNG / Spock see assertj.

scalatest

Configures and runs ScalaTest - Scala-native test framework with multiple specification styles (FlatSpec, FunSuite, WordSpec, FreeSpec, AsyncFlatSpec for async); Matchers DSL (`should equal`, `should contain`, `shouldBe a [Class]`); integrates with ScalaCheck for property-based testing; sbt + Maven + Gradle support; tagged-test selective execution. Use when working with Scala codebases.

testng-tests

Configures and runs TestNG - JVM testing framework with `@Test` priorities + groups + `dependsOnMethods`; `@DataProvider` for parametrized tests with method-level data sources; `testng.xml` suite definitions for grouping + parallelism config; listeners (`ITestListener`, `ISuiteListener`) for hooks; `ITestContext` for cross-test state; integrates with Maven Surefire / Gradle. Use when working with legacy TestNG codebases or needing TestNG-specific features (test method dependencies, suite-level XML config).