. .
JUL 2011
Roman Stumm, 21.07.2011

Mastering Grails TagLibs

The Grails documentation says the „Grails tag library mechanism is simply, elegant and completely reload-able at runtime.“ This is true and thus the Grails documentation about Tag Libraries is very brief. But there are many blog entries on the web that deal pitfalls that occur when one is more concerned with the issue. This article gives you an overview that is as complete as I needed information to understand, write and test Grails TagLibraries. It covers information, that you still do not find in the Grails documentation.

Getting started

If you never had used a tag library with Grails, the first place to start is the documentation. It documents the predefined Grails tags:

You can find a brief introduction into tag libraries:

It tells you

  • where to place the tag-class file (in grails-app/taglib)
  • how to name the tag-class (file name ends with TagLib.groovy)
  • how to implement a simple tag:
class SimpleTagLib {
 def simple = { attrs, body ->
out << body() << attrs.happy == 'true' ? ":-)" : ":-("
}
}
  • and how to use the tag in your .gsp page:
<g:simple happy="true">Hi John</g:simple>

Namespaces

The default namespace of Grails tags is „g“. All tags in all tag libraries can be referenced by their simple name (which should be globally unique to avoid confusions). Your tag library can use an own namespace by declaring a static variable „namespace“

class MyTagLib {  
static namespace = "own"
def doIt = { … }
}

To use the „doIt“ tag, you write in your .gsp right away:

<own:doIt/>

Writing custom tags

Writing your own tag class is easy, but writing unit tests can be tricky, because the Grails environment does a lot of magic at runtime with the tag-library instances. Tipp: You should not subclass an existing TagLib class if you want to extend and reuse its functionality!

Reuse

For example, we want to write our own „select“ tag to select some preconfigured locales in a dropdown list. We create a taglib class under grails-app/taglib named „OwnTagLib.groovy“

class OwnTagLib {
 def chooseLocale = { attrs -> }
}

We want to use the <g:select> tag of grails. This is simple, because we can do the same thing as in the gsp files:

class OwnTagLib {
 def chooseLocale = { attrs ->
 out << g.select(attsr) // reuse
 }
}

At this time our <g:chooseLocale> tag is nothing more than an alias for the <g:select> tag.

Overwrite

One of the strengths of Grails is the scaffolding feature. We can overwrite the grails tags so that the scaffolded views also use our implementation instead of the default tag. If we want to replace the grails <g:localeSelect> tag with our tag, we only rename the Closure:

class OwnTagLib {
 def localeSelect = { attrs ->
 out << g.select(attsr) // reuse
 }
}

 

Overwrite and reuse

If you want to decorate the original implementation with your tag and call it, this is possible:

class OwnTagLib {
 def localeSelect = { attrs ->
  // do something here... 
       // …      
// call the original tag..
      def org =
grailsApplication.mainContext.getBean( 'org.codehaus.groovy.grails.plugins.web.taglib.FormTagLib')
org.localeSelect.call(attrs)
  } 
} 

As you can see, the tag-library instances are spring-beans. You can invoke the closures with call(), but you need to pay attention on the number of parameters:

  • call()
  • call(attrs)
  • call(attrs, body)

depending on the closure that you want to call. I recommend another article about this:

Testing

There is a good article about how to test your taglib with a TagLibUnitTestCase:

So just extend TagLibUnitTestCase and write your test, you find a variable „tagLib“ ready to use when you name the test case OwnTagLibTests according to our example.

  import org.springframework.web.servlet.support.RequestContextUtils as RCU 
 class OwnTagLibTests extends TagLibUnitTestCase {void testLocaleSelect() {
 def output = tagLib.localeSelect([name: 'locales', value: RCU.getLocale(tagLib.request)]).toString()
 assert output.contains('select name="locales"')
 }
}

This works for simple taglibs, but in our example the test fails with a

groovy.lang.MissingPropertyException: No such property: grailsApplication for class OwnTagLib

Mocking

This is where mocking and metaclass programming comes into play. We need to provide the following before we can test the localeSelect-tag (which is based on the <select> tag in FormTagLib):

  • mock grailsAttributes in the tag class
  • mock applicationContext in the grailsAttributes
  • mock the bean messageSource in the applicationContext
  • mock method getMessage in the messageSource
  • inject a reference to another taglib into our implementation
  • mock the g variable in the tag class (which is a NamespacedTagDispatcher during runtime)
  • add dynamic method encodeAsHTML to class String
  • let the test case revert all metaclass changes afterwards

This is a lot of stuff, most of which is only neccessary in our specific situation. If you do not need all of it, i suggest your read this blog article first, which is about mocking grailsAttributes and messageSource, maybe this is enough for you:

For all of you, who what to see the test passing, here is a solution of each thing we need to do in the testcase:

1. Dynamic methods

„grailsAttributes, encodeAsHTML and g“ are provided using dynamic methods on the adequate metaclasses, e.g.

String.class.metaClass.encodeAsHTML = { 
 return delegate; // return the string itself 
}

To let the dynamic method return the string itself, it must use „delegate“ instead of „this“, because „this“ would be the instance of the test case!

2. registerMetaClass

Call registerMetaClass(clazz) for all classes, on which you change the metaclass, so that the test case will revert your changes automatically to avoid side-effects with other tests in the test suites.

3. Mock using StubFor

Variable „grailsAttributes“ implements the interface „ApplicationAttributes“. The groovy mock support lets us create stubs for „grailsAttributes“, „applicationContext“ and „messageSource“ and add behavior for the methods we need in the test:

messageSourceStub = new StubFor(MessageSource.class)
messageSourceStub.demand.getMessage(1..1) {
 code, args, locale ->
return code
}

4. Mock other tag-library

The class GrailsUnitTestCase offers a method „mockTagLib(clazz)“ to mock a tag library. The library to be tested is already prepared as variable „tagLib“ in a subclass of TagLibUnitTestCase. But you can use this method to mock other tag libraries as well and provide reference in the „g“ variable:

	mockTagLib(FormTagLib.class) 
	def originalFormTagLib = new FormTagLib() 	tagLib.metaClass.getG = { return originalFormTagLib } 

5. Mock ConfigurationHolder

If your tag implementation accesses ConfigurationHolder to use values from the Config.groovy of grails, you can programmatically provide some values: Let's assume that the taglib-code to be tested contains:


import org.apache.commons.lang.LocaleUtils
import org.codehaus.groovy.grails.commons.ConfigurationHolder
class OwnTagLib {
def localeSelect = { attrs ->
List<Locale> locales = ConfigurationHolder.config.own.locales.collect {
LocaleUtils.toLocale(it)}
attrs['from'] = locales
out << g.select(attrs)
}
}

You can provide a list of locales in the configuration in your test setup:


 def config = new ConfigObject();
 ConfigurationHolder.setConfig(config);
 config.put('own', ['locales': ['de', 'en']])

I hope you could find some useful hints here. We enjoy your feedback and further suggestions. I admit, the example demonstrates a kind of worst-case-scenario, because it requires us to write much more code in the test to mock dependencies than the production code to be tested. But this is because the <select> tag is a rather complex tag and requires customizations in different situations. Finally, here is the complete setup code for the test required to run the test „testLocaleSelect“:

 def messageSourceStubber
 def originalFormTagLib
 protected void setUp() {
 super.setUp() // do not forget to call super.setUp()
 def config = new ConfigObject();
 ConfigurationHolder.setConfig(config);
 config.put('own', ['locales': ['de', 'en']])
 mockTagLib(FormTagLib.class)
 originalFormTagLib = new FormTagLib()
 mockGrailsAttributesForTagLib(originalFormTagLib)
 tagLib.metaClass.getG = { return originalFormTagLib }
 // extend class String with method encodeAsHTML()
 registerMetaClass(String)
 String.class.metaClass.encodeAsHTML = {
 return delegate; // return the string itself
 }
 // mock a spring-MessageSource with a message getMessage()
 messageSourceStubber = new StubFor(MessageSource.class)
 messageSourceStubber.demand.getMessage(1..1) {code, args, locale -> return code }
}

private void mockGrailsAttributesForTagLib(def theLib) {
 // provide grailsAttributes to the tagLib
 theLib.metaClass.getGrailsAttributes = { getGrailsAttributesStub() }
}

private ApplicationAttributes getGrailsAttributesStub() {
 def applicationContextStubber = new StubFor(org.springframework.context.ApplicationContext.class)
 applicationContextStubber.demand.getBean() {beanName -> getBean(beanName) }

def grailsAttributesStubber = new StubFor(ApplicationAttributes.class)
grailsAttributesStubber.demand.getApplicationContext() { applicationContextStubber.proxyInstance() }
return (ApplicationAttributes) grailsAttributesStubber.proxyInstance()
}

private getBean(String beanName)
{
assertEquals "messageSource", beanName return messageSourceStubber.proxyInstance()
}
(tested with grails 1.3.6)
Kommentare zu Mastering Grails TagLibs Kommentar hinzufügen

Ihr Kommentar







Bitte hier die Zahl 1 eintragen



 
Von