I'm using Spock Framework to write controller tests. Everything is mocked, therefore no calls to third parties. However, I'm getting a Stack Overflow error as below while running this test due to a recursion within Spock Framework.
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1079)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
at com.bsgroup.idpservice.idp.controllers.AuthorizeControllerSpec.should obtain authorized user info(AuthorizeControllerSpec.groovy:37)
Caused by: java.lang.StackOverflowError
at java.base/java.lang.Class.copyMethods(Class.java:3391)
at java.base/java.lang.Class.getMethods(Class.java:1904)
at org.spockframework.util.ReflectionUtil.isObjectMethod(ReflectionUtil.java:111)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:31)
at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
My test class is as below.
package com.abc.idpservice.idp.controllers
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import com.abc.idpservice.BaseMvcSpecification
import com.abc.idpservice.common.constants.AuthConstants
import com.abc.idpservice.idp.data.impl.UserAuthorizationResponseImpl
import com.abc.idpservice.idp.services.IdentityProviderService
@WebMvcTest(controllers = AuthorizeController)
class AuthorizeControllerSpec extends BaseMvcSpecification {
private final DUMMY_USER_ID = '8d826c-4ca6-9c4f-d1a2fa74c965'
private final DUMMY_BUSINESS_UNIT = 'ABC'
public final DUMMY_ROLE = 'ROLE'
@Autowired
IdentityProviderService identityProviderService;
def "should obtain authorized user info" () {
given:
def path = AuthConstants.Rest.REST_API_PREFIX + 'initLoginFlow'
and:
identityProviderService.getAuthorizationInfo(
DUMMY_USER_ID, true) >> UserAuthorizationResponseImpl
.builder()
.businessUnitCode(DUMMY_BUSINESS_UNIT)
.commerceUserId(DUMMY_USER_ID)
.rememberMeFlag(true)
.roles(DUMMY_ROLE)
.build()
when:
def result = mockMvc.perform(MockMvcRequestBuilders.get(path)
.header(AuthConstants.Rest.USER_ID, DUMMY_USER_ID)
.header(AuthConstants.Rest.REMEMBER_ME_FLAG, true)
.header(AuthConstants.Rest.BUSINESS_UNIT_HEADER_PARAM, DUMMY_BUSINESS_UNIT))
then:
result.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath('$.Roles').value(DUMMY_ROLE))
.andExpect(MockMvcResultMatchers.jsonPath('$.BusinessUnitCode').value(DUMMY_BUSINESS_UNIT))
.andExpect(MockMvcResultMatchers.jsonPath('$.CommerceUserID').value(DUMMY_USER_ID))
.andExpect(MockMvcResultMatchers.jsonPath('$.rememberMeFlag').value(true))
}
}
Findings
- This failure happens when the mock request is executed
def result = mockMvc.perform(MockMvcRequestBuilders.get(path)
. - Recursion is as below.
at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
- Spring Boot version
2.5.4
- Spock Framework version
2.0-groovy-3.0
- Test is intended only for the controller. Service call is being mocked as you can see.
Supporting classes
package com.abc.idpservice
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.MockMvc
import com.abc.idpservice.common.context.SpaRequestContext
import com.abc.idpservice.idp.services.IdentityProviderService
import spock.lang.Specification
class BaseMvcSpecification extends Specification {
@Autowired
protected MockMvc mockMvc
@SpringBean
protected SpaRequestContext requestContext = Stub()
protected path(String enpoint) {
"/api/$enpoint"
}
@SpringBean
protected IdentityProviderService identityProviderService = Stub()
}
@Slf4j
@RestController
@RequestMapping("/api")
@AllArgsConstructor
public class AuthorizeController {
private final IdentityProviderService identityProviderService;
@GetMapping("/initLoginFlow")
public UserAuthorizationResponse initLoginFlow(@RequestHeader("userid") String userId,
@RequestHeader("remembermeflag") boolean rememberMeFlag) {
return identityProviderService.getAuthorizationInfo(userId, rememberMeFlag);
}
}
Any help on the cause or solution is appreciated.