Is it even possible to create generic unit test for Spring controller in Spock? I have a abstract controller in Spring Boot, which is extended by a few specific controllers. The result is every controller have the same implementation of CRUD. So, for now I want to create similar unit tests for these controllers, but I cant use constructors in Spock Tests. I get error
CrudControllerTest.groovy
Error:(16, 5) Groovyc: Constructors are not allowed; instead, define a 'setup()' or 'setupSpec()' method
IngredientControllerTest.groovy
Error:(7, 5) Groovyc: Constructors are not allowed; instead, define a 'setup()' or 'setupSpec()' method
For belowe code
abstract class CrudControllerTest<T, R extends JpaRepository<T, Long>, C extends CrudController<T,R>> extends Specification {
private String endpoint
private def repository
private def controller
private MockMvc mockMvc
CrudControllerTest(def endpoint, R repository, C controller) {
this.endpoint = endpoint
this.repository = repository
this.controller = controller
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
def "Should get 404 when product does not exists"() {
given:
repository.findById(1) >> Optional.empty()
when:
def response = mockMvc.perform(MockMvcRequestBuilders.get(endpoint + '/1')).andReturn().response
then:
response.status == HttpStatus.NOT_FOUND.value()
}
}
class IngredientControllerTest extends CrudControllerTest<Ingredient, IngredientRepository, IngredientController> {
IngredientControllerTest() {
def repository = Mock(IngredientRepository)
super("/ingredients", repository, new IngredientController(Mock(repository)))
}
}
Is here any other way to implement generic unit test in Spock?
You can't use constructors for Specifications
instead you can use the template method pattern. Either with separate methods:
abstract class CrudControllerTest extends Specification {
private String endpoint
private def repository
private def controller
private MockMvc mockMvc
def setup() {
endpoint = createEndpoint()
repository = createRepository()
controller = createController()
mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
abstract createEndpoint()
abstract createRepository()
abstract createController()
def "Should get 404 when product does not exists"() {
given:
repository.findById(1) >> Optional.empty()
when:
def response = mockMvc.perform(MockMvcRequestBuilders.get(endpoint + '/1')).andReturn().response
then:
response.status == HttpStatus.NOT_FOUND.value()
}
}
class IngredientControllerTest extends CrudControllerTest<Ingredient, IngredientRepository, IngredientController> {
def createEndpoint() {
"/ingredients"
}
def createRepository {
Mock(IngredientRepository)
}
def createController() {
new IngredientController(repository)
}
}
or with a method that returns everything, which is a bit nicer as some of your values need to reference the other value:
abstract class CrudControllerTest extends Specification {
private String endpoint
private def repository
private def controller
private MockMvc mockMvc
def setup() {
(endpoint, repository, controller) = createSut()
mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
abstract createSut()
def "Should get 404 when product does not exists"() {
given:
repository.findById(1) >> Optional.empty()
when:
def response = mockMvc.perform(MockMvcRequestBuilders.get(endpoint + '/1')).andReturn().response
then:
response.status == HttpStatus.NOT_FOUND.value()
}
}
class IngredientControllerTest extends CrudControllerTest<Ingredient, IngredientRepository, IngredientController> {
def createSut() {
def repo = Mock(IngredientRepository)
["/ingredients", repo, new IngredientController(repository)]
}
}