From qa-unit-tests-jvm
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-jvm:spock-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [spockframework.org/spock/docs][sp-docs]:
Per spockframework.org/spock/docs:
Spock is a Groovy-based JVM test framework. Distinguishing properties:
Mock(), Stub(), Spy()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.
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.
Per sp-docs 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.
Per sp-docs:
| Block | Purpose |
|---|---|
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 [email protected]"]
}
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.
def "user service calls repository"() {
given:
def repo = Mock(UserRepository)
def service = new UserService(repo)
when:
service.create("[email protected]")
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("[email protected]")
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("[email protected]")
then:
1 * spy.save(_) >> { call -> callRealMethod() } // delegate to real impl
}
Mock vs Stub vs Spy:
Mock() - verify-able interactions; throws on uninstructed callsStub() - no verification; default-value response unless instructedSpy() - wraps real object; observe calls + optionally overridethen:
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.
def setupSpec() { /* once before all tests in spec */ }
def cleanupSpec() { /* once after */ }
def setup() { /* before each test */ }
def cleanup() { /* after each test */ }
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-pattern | Why it fails | Fix |
|---|---|---|
| Use Mockito alongside Spock | Two mocking APIs in one suite | Use Spock's built-in (Step 5) |
expect: for multi-step setup | Mixes given + when + then | Use given/when/then explicitly |
Skip 1 * cardinality, just _ * | Loses interaction-count check | Always specify (Step 6) |
| Use Spock for Java-only project | Groovy adds dependency + classpath weight | Use JUnit 5 (Java-native) |
Mock when Stub suffices | Over-strict; tests fail on incidental calls | Stub for non-verified deps (Step 5) |
junit5-tests,
kotest-tests,
testng-tests,
scalatest - sister toolstest-code-conventionsnpx claudepluginhub testland/qa --plugin qa-unit-tests-jvmProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.