How to mock a request when unit testing a service in grails
Asked Answered
B

2

9

I am trying to unit test a service that has a method requiring a request object.

import org.springframework.web.context.request.RequestContextHolder as RCH

class AddressService {

    def update (account, params) {
        try {
            def request = RCH.requestAttributes.request
            // retrieve some info from the request object such as the IP ...
            // Implement update logic
        } catch (all) { 
            /* do something with the exception */ 
        }
    }
}

How do you mock the request object ?

And by the way, I am using Spock to unit test my classes.

Thank you

Brimstone answered 6/3, 2013 at 22:24 Comment(0)
E
8

This code seems to work for a basic unit test (modified from Robert Fletcher's post here):

void createRequestContextHolder() {
    MockHttpServletRequest request = new MockHttpServletRequest()
    request.characterEncoding = 'UTF-8'

    GrailsWebRequest webRequest = new GrailsWebRequest(request, new MockHttpServletResponse(), ServletContextHolder.servletContext)
    request.setAttribute(GrailsApplicationAttributes.WEB_REQUEST, webRequest)

    RequestContextHolder.setRequestAttributes(webRequest)
}

It can be added as a function to your standard Grails unit test since the function name does not start with "test"... or you can work the code in some other way.

Executor answered 11/7, 2013 at 0:31 Comment(0)
U
7

One simple way to mock these, is to modify the meta class for RequestContextHolder to return a mock when getRequestAttributes() is called.

I wrote up a simple spec for doing this, and was quite surprised when it didn't work! So this turned out to be a quite interesting problem. After some investigation, I found that in this particular case, there are a couple of pitfalls to be aware of.

  1. When you retrieve the request object, RCH.requestAttributes.request, you are doing so via an interface RequestAttributes that does not implement the getRequest() method. This is perfectly fine in groovy if the returned object actually has this property, but won't work when mocking the RequestAttributes interface in spock. So you'll need to mock an interface or a class that actually has this method.

  2. My first attempt at solving 1., was to change the mock type to ServletRequestAttributes, which does have a getRequest() method. However, this method is final. When stubbing a mock with values for a final method, the stubbed values are simply ignored. In this case, null was returned.

Both these problems was easily overcome by creating a custom interface for this test, called MockRequestAttributes, and use this interface for the Mock in the spec.

This resulted in the following code:

import org.springframework.web.context.request.RequestContextHolder

// modified for testing
class AddressService {

    def localAddress
    def contentType

    def update() {
        def request = RequestContextHolder.requestAttributes.request
        localAddress = request.localAddr
        contentType = request.contentType
    }
}

import org.springframework.web.context.request.RequestAttributes
import javax.servlet.http.HttpServletRequest

interface MockRequestAttributes extends RequestAttributes {
    HttpServletRequest getRequest()
}

import org.springframework.web.context.request.RequestContextHolder
import spock.lang.Specification

import javax.servlet.http.HttpServletRequest

class MockRequestSpec extends Specification {

    def "let's mock a request"() {
        setup:
        def requestAttributesMock = Mock(MockRequestAttributes)
        def requestMock = Mock(HttpServletRequest)
        RequestContextHolder.metaClass.'static'.getRequestAttributes = {->
            requestAttributesMock
        }

        when:
        def service = new AddressService()
        def result = service.update()

        then:
        1 * requestAttributesMock.getRequest() >> requestMock
        1 * requestMock.localAddr >> '127.0.0.1'
        1 * requestMock.contentType >> 'text/plain'
        service.localAddress == '127.0.0.1'
        service.contentType == 'text/plain'

        cleanup:
        RequestContextHolder.metaClass = null
    }

}
Undirected answered 9/3, 2013 at 18:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.