-->

Grails3 controller integration test case fail: No

2019-05-03 05:42发布

问题:

With just simple following controller action spock integration-test. Here is my Test.

@Integration
@Rollback
class TestControllerSpec extends Specification {

    def setup() {
    }

    def cleanup() {
    }

    void "test something"() {
       setup:
        def c = new TestController()
        c.index()
        expect:
        c.response.contentType !=null
    }
}

getting following Exception

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at grails.web.api.WebAttributes$Trait$Helper.currentRequestAttributes(WebAttributes.groovy:45)
    at grails.web.api.ServletAttributes$Trait$Helper.getRequest(ServletAttributes.groovy:42)

回答1:

I've been doing this and it seems to work fine:

Add field:

@Autowired
WebApplicationContext ctx

In setup(): GrailsWebMockUtil.bindMockWebRequest(ctx)

In cleanup(): RequestContextHolder.resetRequestAttributes()



回答2:

Unfortunately it might be a limitation in Grails 3, that you cannot use integration tests to test controllers.

To integration test controllers it is recommended you use create-functional-test command to create a Geb functional test.

Source from Grails documentation

This seems to be a major change of direction from previous versions of grails. If you really need to test a controller in an integration test, you could try doing this:

NOTE: I realize this might be a bad practice, and it goes against Grails documentation, but sometimes you also need to test things more programmatically, where unit tests aren't sufficient, and are Geb tests aren't granular enough.

@TestFor(TestController) // This will provide a mocked "controller" reference
@Integration
@Rollback
class TestControllerSpec extends Specification {

    // If TestController uses any services, have them autowired into this test
    @Autowired
    SomeService someService

    def setupSpec() {
        // Now connect those services to the controller
        controller.someService = someService
    }

    void "test something"() {
        when:
        controller.index()

        then:
        response.contentType != null
    }
}

WARNING: After some additional work with this format I did find a problem. Using @TestFor will call Holders.clear() when it is complete, which means that there will not be a grailsApplication object in Holders. This will cause problems if you have any integration tests that run after one that uses the approach above. After much digging, it doesn't look like there is an easy (or even hard) way of making this work, which is possibly why it is not supported in Grails 3. That being said, one option is to mark other integration tests with @TestFor, so that the Holders class will be properly populated. Is this a hack? Yes it is! You will need to decide if it is worth the effort of adding this overhead to all tests. In my case it was only one other integration test that needed this (as it is a small application), but if it was more than that I would not use this approach.