Skip to content

Instantly share code, notes, and snippets.

@berngp
Created April 14, 2011 17:53
Show Gist options
  • Save berngp/920058 to your computer and use it in GitHub Desktop.
Save berngp/920058 to your computer and use it in GitHub Desktop.
Unit Testing Custom Object Marshallers with Spock in Grails
package org.osscripters.grails.common.web.converters.marshaller
import grails.plugin.spock.ControllerSpec
import org.codehaus.groovy.grails.support.MockApplicationContext
import org.codehaus.groovy.grails.web.converters.ConverterUtil
import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationInitializer
import org.springframework.validation.Errors
import org.springframework.web.context.WebApplicationContext
/**
* User: berngp
* Date: 4/13/11
* Time: 1:46 PM
*/
abstract class MarshallerSpecSupport extends ControllerSpec {
protected def applicationContext
def setup() {
mockApplicationContext()
registerDefaultConverters()
registerCustomConverters()
}
protected WebApplicationContext mockApplicationContext() {
applicationContext = new MockApplicationContext()
applicationContext
}
protected void registerDefaultConverters() {
def convertersInit = new ConvertersConfigurationInitializer()
convertersInit.initialize()
[List, Set, Map, Errors, Enum].each { addConverters(it) }
}
abstract protected void registerCustomConverters()
protected void addConverters(Class clazz) {
registerMetaClass(clazz)
clazz.metaClass.asType = {Class asClass ->
if (ConverterUtil.isConverterClass(asClass)) {
return ConverterUtil.createConverter(asClass, delegate, applicationContext)
}
else {
return ConverterUtil.invokeOriginalAsTypeMethod(delegate, asClass)
}
}
}
}
package org.osscripters.grails.common.web
class RestServiceException extends org.apache.commons.lang.exception.NestableException {
RestServiceException(java.lang.String msg) {
super(msg)
}
RestServiceException(java.lang.Throwable cause) {
super(cause)
}
RestServiceException(java.lang.String msg, java.lang.Throwable cause) {
super(msg, cause)
}
RestServiceStatusCode getErrorCode() {
//Enum wrapping HttpServletResponse.SC_*
RestServiceStatusCode.INTERNAL_SERVER_ERROR
}
}
package org.osscripters.grails.common.web.converters.marshaller
import org.osscripters.grails.common.web.converters.marshaller.ContextAwareObjectMarshallerTemplate
import org.osscripters.grails.common.web.RestServiceException
import grails.converters.JSON
import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException
import org.codehaus.groovy.grails.web.json.JSONWriter
import org.gcontracts.annotations.Requires
/** */
class RestServiceExceptionJSONMarshaller extends ContextAwareObjectMarshallerTemplate<JSON> {
boolean supports(Object object) {
object instanceof RestServiceException
}
@Requires({supports(object)})
void marshalObject(Object object, JSON json) throws ConverterException {
RestServiceException exception = object as RestServiceException
JSONWriter writer = json.writer
try {
writer.object()
addBaseline(exception, json)
addCause(exception, json)
writer.endObject()
}
catch (ConverterException ce) {
throw ce
}
catch (anyOtherException) {
throw new ConverterException(
"Error converting throwable with class ${object.class.name}",
anyOtherException)
}
}
private addBaseline(RestServiceException exception,
JSON json) {
json.property('type', exception.class.name)
json.property('errorCode', exception.errorCode.value())
json.property('message', getMessageFromThrowable(exception) )
}
private addCause(RestServiceException exception,
JSON json) {
if (!exception?.cause) {
return
}
Throwable cause = exception.cause
json.property( 'cause',[ 'type':cause.class.name, 'message':getMessageFromThrowable(cause) ] )
}
private String getMessageFromThrowable(Throwable throwable){
getMessage( throwable?.message) ?: throwable?.message
}
}
package org.osscripters.grails.common.web.converters.marshaller
import org.osscripters.grails.common.web.converters.marshaller.MarshallerSpecSupport
import org.osscripters.grails.common.web.RestServiceException
import grails.converters.JSON
import grails.converters.XML
import spock.lang.Unroll
import spock.lang.Shared
/**
* User: berngp
* Date: 4/12/11
* Time: 2:46 PM
*/
class RestServiceExceptionMarshallerSpec extends MarshallerSpecSupport {
def expectedJSONWithCause = { Map args ->
"""{
"type":"${args.type}",
"errorCode":${args.errorCode.value()},
"message":"${args.message}",
"cause":{"type":"$args.cause.type", "message":"$args.cause.message"}
}"""
}
def expectedJSON = { Map args ->
"""{
"type":"${args.type}",
"errorCode":${args.errorCode.value()},
"message":"${args.message}"
}"""
}
def expectedXMLWithCause = { Map args ->
"""<?xml version="1.0" encoding="UTF-8"?>
<error>
<type>${args.type}</type>
<errorCode>${args.errorCode.value()}</errorCode>
<message>${args.message}</message>
<cause>
<type>$args.cause.type</type>
<message>$args.cause.message</message>
</cause>
</error>"""
}
def expectedXML = { Map args ->
"""<?xml version="1.0" encoding="UTF-8"?>
<error>
<type>${args.type}</type>
<errorCode>${args.errorCode.value()}</errorCode>
<message>${args.message}</message>
</error>"""
}
@Shared
Throwable KNOWN_CAUSE = new IllegalStateException('Cause Exception')
@Unroll("marshall 'Service Exception' as #format with cause #cause")
def 'marshall simple service exception'() {
given:
controller.throwable = new RestServiceException('Expected Message', cause)
and:
String expected = buildExpected(format, controller.throwable)
when:
controller."test${format}Throwable"()
and:
String obtained = mockResponse.contentAsString
then:
parser.parse(expected) == parser.parse(obtained)
where:
format | parser | cause
'JSON' | JSON | null
'XML' | XML | null
'JSON' | JSON | KNOWN_CAUSE
'XML' | XML | KNOWN_CAUSE
}
String buildExpected(String format, RestServiceException throwable) {
Map args = [type: throwable.class.name, errorCode: throwable.errorCode, message: throwable.message]
if (throwable?.cause) {
args << [cause: [type: throwable.cause.class.name, message: throwable.cause.message]]
this."expected${format}WithCause"(args)
} else {
this."expected${format}"(args)
}
}
def provideMvcClassUnderTest() {
TestController.class
}
protected void registerCustomConverters() {
addConverters(RestServiceException)
JSON.registerObjectMarshaller(new RestServiceExceptionJSONMarshaller())
XML.registerObjectMarshaller(new RestServiceExceptionXMLMarshaller())
}
class TestController {
RestServiceException throwable
def testJSONThrowable = {
render throwable as JSON
}
def testXMLThrowable = {
render throwable as XML
}
}
}
package org.osscripters.grails.common.web.converters.marshaller
import org.osscripters.grails.common.web.converters.marshaller.ContextAwareObjectMarshallerTemplate
import org.osscripters.grails.common.web.RestServiceException
import grails.converters.XML
import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException
import org.codehaus.groovy.grails.web.converters.marshaller.NameAwareMarshaller
import org.gcontracts.annotations.Requires
/** */
class RestServiceExceptionXMLMarshaller extends ContextAwareObjectMarshallerTemplate<XML>
implements NameAwareMarshaller {
boolean supports(Object object) {
return object instanceof RestServiceException
}
@Requires({ supports(object) })
void marshalObject(Object object, XML xml) throws ConverterException {
RestServiceException throwable = object as RestServiceException
try {
addBaseline(throwable, xml)
addCause(throwable, xml)
}
catch (ConverterException ce) {
throw ce
}
catch (anyOtherException) {
throw new ConverterException("Error converting Bean with class ${object.class.name}",
anyOtherException)
}
}
private addBaseline(RestServiceException throwable, XML xml) {
xml.startNode('type').chars(throwable.class.name).end()
xml.startNode('errorCode').chars( throwable.errorCode.value() as String ).end()
xml.startNode('message').chars(getMessageFromThrowable(throwable)).end()
}
private addCause(RestServiceException exception, XML xml) {
if (!exception?.cause) {
return
}
Throwable cause = exception.cause
xml.startNode('cause')
xml.startNode('type').chars(cause.class.name).end()
xml.startNode('message').chars(getMessageFromThrowable(cause)).end()
xml.end()
}
private String getMessageFromThrowable(Throwable throwable){
getMessage( throwable?.message) ?: throwable?.message
}
String getElementName(Object o) {
'error'
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment