0

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

  1. This failure happens when the mock request is executed def result = mockMvc.perform(MockMvcRequestBuilders.get(path).
  2. Recursion is as below.
    at org.spockframework.spring.mock.DelegatingInterceptor.intercept(DelegatingInterceptor.java:53)
    at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
  1. Spring Boot version 2.5.4
  2. Spock Framework version 2.0-groovy-3.0
  3. 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.

Vy Do
  • 46,709
  • 59
  • 215
  • 313
Nipun Thathsara
  • 1,119
  • 11
  • 20
  • Maybe Leonard can answer this just by looking at your code. I cannot. It would be nice to have an [MCVE](https://stackoverflow.com/help/mcve) on GitHub. If you could prepare one, that would be awesome. – kriegaex Jan 06 '22 at 00:36
  • Given the answer of @Nipun I would assume, that injecting again via `@Autowired IdentityProviderService identityProviderService` in `AuthorizeControllerSpec`, basically shadowing the inherited field, instead of just using `@SpringBean IdentityProviderService identityProviderService = Stub()` of `BaseMvcSpecification` caused the issue. – Leonard Brünings Jan 07 '22 at 14:27

1 Answers1

0

one of my colleagues pointed out the issue. Issue turned out to be the use of @Autowired in the test class. Using the @SpringBean instead, resolved the issue.

Thanks everyone for your help.

Cheers

Nipun Thathsara
  • 1,119
  • 11
  • 20