Spock with Spring Boot: Integration Testing Setup and @SpringBean
Spock integrates with Spring Boot via the spock-spring module, which brings @SpringBootTest, @SpringBean, and @SpringSpy into Spock specs. You get the full Spring context with Spock's readable BDD syntax, mock injection via @SpringBean, and real bean overrides via @SpringSpy. This guide covers setup, common patterns, and how to test REST endpoints with MockMvc.
Setup: Adding Spock to a Spring Boot Project
Add to build.gradle:
dependencies {
testImplementation 'org.spockframework:spock-core:2.4-groovy-4.0'
testImplementation 'org.spockframework:spock-spring:2.4-groovy-4.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// For Groovy compilation
testImplementation 'org.apache.groovy:groovy:4.0.21'
}Spock specs must live in src/test/groovy/. Spring Boot's test autoconfiguration picks them up automatically.
@SpringBootTest — Full Context Integration Tests
The simplest integration test loads the full application context:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserApiSpec extends Specification {
@Autowired
TestRestTemplate restTemplate
def "GET /users returns 200 and list"() {
when:
def response = restTemplate.getForEntity("/users", List)
then:
response.statusCode == HttpStatus.OK
response.body.size() >= 0
}
}WebEnvironment.RANDOM_PORT starts a real embedded server. WebEnvironment.MOCK (the default) uses a mock servlet environment — faster but doesn't exercise filters and servlet containers.
Application Properties for Tests
Override properties in test specs:
@SpringBootTest
@TestPropertySource(properties = [
"spring.datasource.url=jdbc:h2:mem:testdb",
"feature.flag.new-ui=true"
])
class FeatureFlagSpec extends Specification { ... }Or create src/test/resources/application-test.yaml and activate the profile:
@SpringBootTest
@ActiveProfiles("test")
class ServiceSpec extends Specification { ... }@SpringBean — Injecting Mocks into the Spring Context
@SpringBean is the Spock equivalent of Spring's @MockBean. It creates a Spock mock and registers it as a Spring bean, replacing any existing bean of that type:
@SpringBootTest
class OrderServiceSpec extends Specification {
@SpringBean
PaymentGateway paymentGateway = Mock()
@SpringBean
EmailService emailService = Stub()
@Autowired
OrderService orderService
def "order placement calls payment gateway"() {
given:
def order = new Order(total: 99.99)
when:
orderService.place(order)
then:
1 * paymentGateway.charge(99.99, _)
}
}The @SpringBean field must be initialized with Mock(), Stub(), or Spy() in the field declaration — not in setup().
Why @SpringBean instead of field injection + Mock()? If you just declare def paymentGateway = Mock(PaymentGateway) without @SpringBean, Spring injects the real bean into orderService. Your mock exists but isn't what the service uses. @SpringBean replaces the Spring context's bean with your mock.
@SpringBean for Interfaces and Final Classes
For interfaces, Spock creates a JDK proxy. For concrete classes, it uses CGLIB subclassing. If you're mocking a final class, add the spock-mock-maker-inline dependency:
testImplementation 'org.spockframework:spock-mock-maker-inline:2.4-groovy-4.0'Then declare in src/test/resources/org/mockito/plugins/MockMaker:
mock-maker-inlineThis uses ByteBuddy to create mocks of final classes.
@SpringSpy — Spying on Real Beans
@SpringSpy is the equivalent of Spring's @SpyBean. It wraps the real Spring bean with a Spock spy:
@SpringBootTest
class AuditSpec extends Specification {
@SpringSpy
AuditService auditService
@Autowired
UserService userService
def "user creation is audited"() {
when:
userService.createUser("alice", "alice@example.com")
then:
1 * auditService.log(AuditEvent.USER_CREATED, _)
}
}The real auditService bean is called — but Spock records the interactions. You can verify calls happened AND let the real implementation run.
To override specific methods:
@SpringSpy
AuditService auditService = {
isAuditEnabled() >> false
}Testing REST Controllers with MockMvc
For controller-layer tests without a full server, use @WebMvcTest with MockMvc:
@WebMvcTest(UserController)
class UserControllerSpec extends Specification {
@Autowired
MockMvc mockMvc
@SpringBean
UserService userService = Mock()
def "GET /users/:id returns 200 with user data"() {
given:
userService.findById(42L) >> new User(id: 42, name: "Alice")
when:
def result = mockMvc.perform(get("/users/42"))
.andReturn()
then:
result.response.status == 200
result.response.contentAsString.contains("Alice")
}
def "GET /users/:id returns 404 when not found"() {
given:
userService.findById(99L) >> null
when:
def result = mockMvc.perform(get("/users/99"))
.andReturn()
then:
result.response.status == 404
}
}Import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* for the get(), post(), put(), delete() helpers.
Testing POST Endpoints with JSON Body
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.http.MediaType
def "POST /users creates user and returns 201"() {
given:
def requestBody = '{"name": "Bob", "email": "bob@example.com"}'
userService.create(_ as CreateUserRequest) >> new User(id: 1, name: "Bob")
when:
def result = mockMvc.perform(
post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody)
).andReturn()
then:
result.response.status == 201
1 * userService.create({ it.name == "Bob" })
}Context Caching
Spring Boot caches the application context between tests to avoid reloading it for every spec. Tests that use @SpringBean or @SpringSpy create a modified context and may not share the cache with tests that don't.
To maximize cache reuse:
- Put common
@SpringBeandeclarations in a base spec class - Use
@TestConfigurationbeans for shared test infrastructure - Avoid
@DirtiesContextunless necessary — it forces a full context reload
// Base class with common mocks
@SpringBootTest
abstract class BaseIntegrationSpec extends Specification {
@SpringBean
EmailService emailService = Stub()
@SpringBean
NotificationService notificationService = Stub()
}
class OrderSpec extends BaseIntegrationSpec {
// Inherits the stubs — shares context with other BaseIntegrationSpec subclasses
@Autowired
OrderService orderService
}Transactions in Integration Tests
Use @Transactional to roll back database changes after each test:
@SpringBootTest
@Transactional
class UserRepositorySpec extends Specification {
@Autowired
UserRepository userRepository
def "find by email returns user"() {
given:
userRepository.save(new User(email: "alice@example.com"))
when:
def found = userRepository.findByEmail("alice@example.com")
then:
found.present
found.get().email == "alice@example.com"
}
// transaction rolled back — DB is clean for next test
}Note: @Transactional doesn't work with WebEnvironment.RANDOM_PORT — the test runs in a different transaction than the server. For those cases, manually clean up in cleanup() or use @DirtiesContext.
Data-Driven Integration Tests
Combine Spring context with Spock's where: block for parameterized integration tests:
@SpringBootTest
class ValidationSpec extends Specification {
@Autowired
UserService userService
@Unroll
def "invalid email '#email' is rejected"() {
when:
def result = userService.validateEmail(email)
then:
!result.valid
result.message == expectedError
where:
email | expectedError
"" | "Email is required"
"notanemail" | "Invalid email format"
"missing@tld" | "Invalid domain"
"@nodomain.com" | "Missing local part"
}
}The Spring context loads once and is reused for all four iterations.
Common Pitfalls
@SpringBean not initialized in field declaration. You cannot do:
@SpringBean
PaymentGateway paymentGateway // ERROR: must be initialized
// Correct:
@SpringBean
PaymentGateway paymentGateway = Mock()@Autowired with @SpringBean on the same field. You inject with @Autowired, mock with @SpringBean — they're different fields for different purposes.
Missing spock-spring dependency. Without it, @SpringBean is not recognized and acts as a plain field annotation — tests appear to work but use the real bean.
Context pollution across specs. If one spec modifies a @Shared bean's state, other specs see it. Use @DirtiesContext on the offending spec, or reset the state in cleanupSpec().
Summary
Spock integrates with Spring Boot through spock-spring, giving you @SpringBootTest context loading, @SpringBean for mock injection, and @SpringSpy for partial bean overrides. You write the same readable given/when/then specs — the Spring context is just another collaborator. Use @WebMvcTest for controller slices, @Transactional for DB rollback, and base spec classes to share context across tests. The result is integration tests that are as readable as unit tests, with the full power of the Spring context behind them.