Created
April 14, 2011 17:53
-
-
Save berngp/920058 to your computer and use it in GitHub Desktop.
Unit Testing Custom Object Marshallers with Spock in Grails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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