Showing posts with label taglibs. Show all posts
Showing posts with label taglibs. Show all posts

Friday, 17 September 2010

Clean TagLib Tests

Despite my best efforts I've always found it hard to write clean taglib tests. Now thanks to Spock and GroovyShell things are getting easier...

void setup() {
mockDomain Invoice
}

def "Attachments icon has correct markup"() {
given:
Invoice invoice = new InvoiceBuilder().buildAndSave()

when:
renderAttachmentsIcon([target: invoice])

then:
valueOf('img.@id') == "toggle-attachments-${invoice.id}"
valueOf('script') == "\$('#toggle-attachments-${invoice.id}').bind('click', Books.attachments.toggle);"
}

Because I want to use GPath make assertions about the resulting HTML I've overriden TagLibSpec's methodMissing closure as follows...

def methodMissing(String name, args) {
String html = super.methodMissing(name, args)
createDocument(html)
}

void createDocument(String html) {
String xml = "<results>${html}</results>"
document = new XmlSlurper().parseText(xml)
}

And added helper methods for evaluating GPath expressions...

String valueOf(String gPath) {
evaluate(gPath).text()
}

GPathResult evaluate(String gPath) {
new GroovyShell(new Binding(document: document)).evaluate("document.${gPath}")
}

There's a little bit more to this story unfortunately...

Firstly my taglibs use the MarkupBuilder and when run from my Spock test this only outputs the opening tag of the first element I genererate. I haven't had a chance to look into this yet, but a workaround is to add "out << '' to the end of the taglib method

Secondly Spock interactions aren't yet as powerful as gmock, so I usually end up adding code to (g)mock grails taglibs.

Thirdly another one of my tests outputs &nbsp; in the HTML which causes the XML parsing to barf. The solution is to map the &nbsp entity to a known character (in this case underscore).

Finally there was a bug in Grails 1.3.3 / Spock 0.5 which breaks mockDomain. This is reportedly fixed in 1.3.4 and the latest Spock code, but I haven't tried upgrading yet.

Here's how things really look...

@WithGMock
class MetaAttachmentsTagLibSpec extends TagLibSpec {

def g
def document

void setup() {
g = mock()
mock(tagLib).getG().returns(g).stub()
mockDomain Invoice

PluginManagerHolder.pluginManager = [hasGrailsPlugin: { String name -> true }] as GrailsPluginManager // Workaround for JIRA GRAILS-6482
}

def cleanup() {
PluginManagerHolder.pluginManager = null // Workaround for JIRA GRAILS-6482
}

def "Attachments icon has correct markup"() {
given:
Invoice invoice = new InvoiceBuilder().buildAndSave()
g.resource(instanceOf(Map)).returns '/foo.jpg'

when:
renderAttachmentsIcon([target: invoice])

then:
valueOf('img.@id') == "toggle-attachments-${invoice.id}"
valueOf('img.@src') == "/foo.jpg"
valueOf('script') == "\$('#toggle-attachments-${invoice.id}').bind('click', Books.attachments.toggle);"
}

def methodMissing(String name, args) {
String html
play {
html = super.methodMissing(name, args)
}
createDocument(html)
}

def createDocument(String html) {
String xml = """<!DOCTYPE html [<!ENTITY nbsp "_">]>\n<results>${html}</results>"""
document = new XmlSlurper().parseText(xml)
}
}

And the method under test...

def renderAttachmentsIcon = { Map attrs, def body ->
String targetId = attrs.target.id
String iconId = "toggle-attachments-${targetId}"
String imgSrc = g.resource(dir:'/images/skin', file:'paperclip.png')

MarkupBuilder builder = new MarkupBuilder(out)
builder.img(id: iconId, src: imgSrc)
builder.script(type:'text/javascript') {
mkp.yield "\$('#${iconId}').bind('click', Books.attachments.toggle);"
}
out << '' // flush for unit tests
}

Friday, 8 January 2010

Ham and Chips

Rob Fletcher's blog post on gMock inspired me to try out hamcrest, but I was surprised to find out there's no simple way to match collections in mocked method arguments - the best I could come up with was this...
mock(tagLib).fieldLabel(instanceOf(Map), instanceOf(Closure)).returns("<label></label>")
which doesn't tell me whether I'm calling fieldLabel with the expected attributes. After a bit of searching I found the MapContentMatcher in the Spring Integration library, but didn't want include the whole jar just for that. It also had dependencies other libraries and was heavily typed with generics. Here's the cut down groovy version...
import org.hamcrest.*
import static org.hamcrest.core.AllOf.*
import org.hamcrest.Factory as HamcrestFactory

class MapContentMatcher extends TypeSafeDiagnosingMatcher<Map> {

Object key
Object value

MapContentMatcher(Object key, Object value) {
this.key = key
this.value = value
}

public boolean matchesSafely(Map actual, Description mismatchDescription) {
return actual.containsKey(key) && value == actual[key]
}

public void describeTo(Description description) {
description.appendText("$key:$value")
}

@HamcrestFactory
public static Matcher hasAllEntries(Map expected) {
return allOf(expected.collect { k, v ->
new MapContentMatcher(k, v)
})
}
}

Which can be used thus...
mock(tagLib).fieldLabel(hasAllEntries([id: expectedField, label:expectedHeading]), instanceOf(Closure)).returns("<label></label>")

Thursday, 24 April 2008

Dynamic methods on domain classes, controllers and taglibs

If you want to dig into what the various dynamic methods attached to Grails artefacts actually do under the hood you can find all the code in:
kthxbye