Grails—Unit Testing View Rendering (with custom TagLib namespace quirk)

Grails, programming, test

Background

GroovyPageUnitTestMixin

From the Grails guide, testing a view in a unit test involves:

  • Annotating the Specification with: @TestMixin(GroovyPageUnitTestMixin).
  • Calling render(view: '...', model: ...) or render(template: '...', model: ...) whose returned value will be a String of the HTML output.

Custom TagLibs

Normally, without doing anything else, if the view or template uses custom (i.e. not ones from the g: namespace) TagLibs, they will not be expanded. For example, if a view GSP was like this:

<div id="blah">
    <abc:decorate code="${obj}" format="a" />
</div>

the rendered content for the TagLib will be pretty much verbatim of the original GSP. The only exception is that any ${...} will be evaluated. For example:

<div id="blah">
    <abc:decorate code="Object@3234f21" format="a" />
</div>

And from the Grails guide, using mockTagLib(DecorateTagLib) (assuming the TagLib class for the abc:decorate tag is implemented in the TagLib class DecorateTagLib) will then trigger the TagLib:

@TestMixin(GroovyPageUnitTestMixin)
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
        mockTagLib(DecorateTagLib)

        expect:
        ...
    }
}

will now render something like:

 
<div id="blah">
    {output of DecorateTagLib.decorate}
</div>

Note that the same effect can be accomplished using the @Mock() annotation on the class:

@TestMixin(GroovyPageUnitTestMixin)
@Mock([DecorateTagLib])
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
            ...

        expect:
        ...
    }
}

And Now the Quirk…

If there were multiple TagLib classes sharing the same namespace (abc in this example) used in the GSP:
DecorateTagLib.groovy

class DecorateTagLib {
    static namespace = 'abc'

    def decorate = { attrs -> ... }
}

and FormatTagLib.groovy

class FormatTagLib {
    static namespace = 'abc'

    def format = { attrs -> ... }
}

and the GSP uses them both:

<div id="blah">
    <abc:decorate code="${obj}" format="a" />
    <abc:format val="${x}" />
</div>

and only some of them was mocked:

@TestMixin(GroovyPageUnitTestMixin)
@Mock([DecorateTagLib])
// NOTE there is no mocking of the FormatTagLib class implementing abc:format
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
            ...

        expect:
        ...
    }
}

then running the test will bring up and error for the TagLibs that are not mocked. For example, the error will be like:
Tag [format] does not exist. No tag library found for namespace: abc

To fix this scenario, either:

  • Mock all the TagLibs used in the GSP that share the same namespace (e.g. abc in these examples), or
  • Redistribute the TagLibs using unique namespaces so that they do not share the same namespace. However, note that the rule stays the same for all the individual namespaces:
    • When at least one TagLib from a namespace is mocked, ALL other TagLibs used in the GSP with that namespace must also be mocked.

Grails’s RESTClient

Grails, groovy, programming

The groovy.net.http.RESTClient class is a subclass of a more general groovyx.net.http.HttpBuilder class and comes with the “rest” plugin (http://grails.org/plugin/rest).

GET

A simple GET request with query parameters:

def rest = new RESTClient('http://www.my.com')
try {
// equivalent to: http://www.my.com/abc/123?param1=p1&param2=p2&param3=p3a&param3=p3b
def response = rest.get([
path:'/abc/123',
contentType:'application/json',
query:[param1:'p1', param2:'p2', param3:['p3a','p3b']])

// Assuming the server respects Content-Type and returns a JSON,
// the follow will provide the JSONObject or JSONArray
// for the returned results
def json = response.data
...
} catch (HttpResponseException ex) {
// errors and response status codes >= 400 end up here
}

POST

A sample for POSTing data w/ JSON

def rest = new RESTClient('http://www.my.com')
try {
// equivalent to: POST http://www.my.com/abc/123 with body of '{"param1": 1, "param2": "2"}'
def response = rest.post(path:'/abc/123',
contentType:'application/json',
body: [param1: 1, param2: "2"])
json = response.data
} catch (HttpResponseException ex) {
...
}

groovy.net.http.HttpBuilder uses the RequestConfigDelegate class to parse the map provided to get() and post(). The map supports these keys:

  • uri — used to specify the complete URL for the request, excluding the query component.  If used with path, then this should be the base URL. This value overrides the value passed to the ctor of RESTClient
  • query — used to specify a map of values representing the query parameters.  Lists are supported (see the GET example above).
  • path — used in conjunction with uri if you want to separate the path and base components of a URL.
  • contentType — the Content-Type to use and accept (either a String literal such as ‘application/json’ or a groovyx.net.http.ContentType constant).
  • requestContentType — if the request body type is different than the response (Accepts) body type, then this value is used to specify the request’s content type.
  • body — the body of the request
  • headers — used to specify a map of headers for the request

No support for URL fragments (as of rest-0.8)

You will need to create a java.net.URI instance with the entire URL w/ query and fragment and pass it to the ctor of RESTClient.

Response

By default, get() and post() will return an instance of groovyx.net.http.HttpResponseDecorator.  Commonly used methods are isSuccess() and getData().

  • isSuccess() returns true if the HTTP status code is < 400.
  • getData() will return the parsed body, and the type depends on the contentType used for the get() or post() method.

 

Memcached in Grails: a quickie

Grails, programming

Two clients will be covered: XMemcached and spymemcached

Xmemcached

Add dependency

Add in BuildConfig.groovy:

dependencies {
...
compile 'com.googlecode.xmemcached:xmemcached:1.4.2'
}

Set up the bean

Add in conf/spring/resources.groovy:

beans = {
...
memcachedClient(net.rubyeye.xmemcached.utils.XMemcachedClientFactoryBean) {
servers="localhost:11211"
}
}

Usage in a service

class MyService {
static final int expiry = 30 // 30s

def memcachedClient

def perform() {
def val = memcachedClient.get('key')
if (!val) {
// cache miss
...
// put in cache
memcachedClient.set('key', expiry, val)
}
// use val
}
...
}

Spy Memcached

Add dependency

Add in BuildConfig.groovy:

dependencies {
...
compile 'net.spy:spymemcached:2.9.1'
}

Set up the bean

Add in conf/spring/resources.groovy:

beans = {
...
memcachedClient(net.spy.memcached.spring.MemcachedClientFactoryBean) {
servers="localhost:11211"
}
}

Usage in a service


class MyService {
static final int expiry = 30 // 30s

def memcachedClient

def perform() {
def val = memcachedClient.get('key')
if (!val) {
// cache miss
...
// put in cache
memcachedClient.set('key', expiry, val)
}
// use val
}
...
}

References

Notes on multiple Grails apps sharing a DB

Grails, programming

Multiple Apps

Two logical partitions of services, perhaps one public-facing with GSPs serving up UI and one headless with REST services, can be easier to maintain if implemented as separate Grails apps.

If there is a breaking change in a domain (and corresponding tables) in one app, for instance, it can be re-deployed without disrupting the other app as long as that other app doesn’t use the domain in question.

Shared domains in in-place plugins

On the other hand, there may be domains that are relevant and used by both apps. One way to make this work is to put them in a plug-in, referenced by both apps. This plug-in can even be an in-place plug-in if the iteration of editing and publishing the plug-in proves tedious during development.

The trouble with in-place plugins

Various tools support an evolving in-place plug-in with varying success (Groovy/Grails Tool Suite 3.3.0, for instance, doesn’t even seem to recognize them). IntelliJ IDEA 12.1 Ultimate (the paid version) seems to recognize them and handles them fine most of the time … because even Grails itself seems flaky at times, requiring periodic cleaning up of cached binaries of apps that use in-place plugins:

rm -rf ~/.grails/.slcache
rm -rf ~/.grails/2.2.3/projects/myApp

database-migration

After about a week of Grails development, most people will realize that they’ve outgrown that dataSource’s dbCreate="create" option. And for a while after that point, perhaps dbCreate="update" may buy some more time. Beyond that, some real SQL migration tool is needed.

Rails has “rake db:migrate“, and Grails has “grails dbm-update” with the database-migration plugin (currently 1.3.5).

Unfortunately, while the database-migration plugin works great for a single Grails app connecting to an SQL DB, it is very cumbersome to get working for multiple apps sharing an SQL DB.

Disjoint domains

If app1 has domain A and app2 has domain B, and the shared DB naturally has tables a and b, running dbm-gorm-diff in app1 will give you a changeset that will attempt to drop table b. Similarly, running dbm-gorm-diff in app2 will give you a changeset that will attempt to drop table a. This is because app1 doesn’t know about domain B and therefore table b. The dbm-gorm-diff tool will assume that domain B has been deleted, so it dutifully adds an entry to clean up table b for you. And similarly, running dbm-gorm-diff in app2 will add an entry to drop table a.

One way I found to get around this is to add app2’s tables (including joining tables and sequences thereof) to app1’s grails.plugin.databasemigration.ignoredObjects and app1’s tables and friends into app2’s grails.plugin.databasemigration.ignoredObjects.

But that’s pretty tedious work and a maintenance pain.

Shared domains

Then there are the placements of these changelog files. The dbm-* tools look in only one place for them: grails-app/migration. So that means there are N lists of these files if I have N apps sharing a DB. So in the least there is a duplicate of the base/root changelog file in each app’s grails-app/migrations/. Then each app has its own changelog files for changes to its own domains.

What about shared domains? Those from the in-place plugin? Where do changelogs for those domains go? To avoid problems, of course, they need to go into all the apps (not strictly necessary, but for consistency and documentation, better to replicate these), just like the base/root changelog.

So MORE maintenance steps.

At this point one wonders if it’s better to craft these changelogs manually. But for the most part, dbm-gorm-diff saves a lot of work. So I’m not entirely ready to let it go yet.

Policy

I presented this to my team, and they agreed that maintaining separate changelog files and ignored lists are tedious and error-prone. We ended up biting the bullet and take the one-schema strategy:

  • All domains in general should reside in the shared in-place plug-in, whether they were actually shared between apps or not
  • Pick one of the apps from which to perform database-migration tasks, and put the changelogs in that app

The result is that each app gets the union of all the domains, and each of the apps use a proper subset of that group of domains. It’s not as lean and pure, but it’s a good compromise, I believe.

So when there is a schema change, we now need to assess the impacted apps and redeploy each as needed.

Grails unit vs integration tests

Grails, programming, test

Services

Content here applies to Grails 2.1.3 and may also apply to newer versions.

Setup

We start with three service classes: OneService, TwoService, and ThreeService. OneService uses TwoService which uses ThreeService.

class ThreeService {
def hello() {
"Hello from Three"
}
}

class TwoService {
def threeService
def hello() {
"Hello from Two:" + threeService.hello()
}
}

class OneService {
def twoService
def hello() {
"Hello from One:" + twoService.hello()
}
}

Calling OneService#hello() would normally return "Hello from One:Hello from Two:Hello from Three"

Unit Test

Add a unit test for OneService:

@TestFor(OneService)
class OneServiceTests {
void testSomething() {
defineBeans {
twoService(TwoService)
threeService(ThreeService)
}
service.hello()
}
}

Run it and get this:

| Failure: testSomething(org.van.OneServiceTests)
| java.lang.NullPointerException: Cannot invoke method hello() on null object
at org.van.TwoService.hello(TwoService.groovy:7)
at org.van.OneService.hello(OneService.groovy:7)

The defineBeans{ ... } trick only works for one level, it seems. It got AService#bService hooked up, but BService#cService remained null.

So what happens is that OneService#hello() got to call TwoService#hello() OK. However, when TwoService#hello() tries to call ThreeService#hello(), we get a NPE because TwoService#threeService was not properly injected.

Integration Test

One way to test the flow is to use an integration test instead:

class OneServiceIntTestTests {

@Test
void testSomething() {
OneService oneService = new OneService()
TwoService twoService = new TwoService()
ThreeService threeService = new ThreeService()
twoService.threeService = threeService
oneService.twoService = twoService
oneService.hello()
}
}

While this works, we will need to do the injection plumbing ourselves.

Controllers

Setup

Add a TwoController class:

class TwoController {
def twoService

def index() {
render twoService.hello()
}
}

Accessing the controller returns Hello from Two:Hello from Three as expected.

Unit Test

Add a unit test for TwoController:

@TestFor(TwoController)
class TwoControllerTests {
void testSomething() {
defineBeans {
twoService(TwoService)
threeService(ThreeService)
}
controller.index()
}
}

And it fails similarly to OneServiceTests:

| Failure: testSomething(org.van.TwoControllerTests)
| java.lang.NullPointerException: Cannot invoke method hello() on null object
at org.van.TwoService.hello(TwoService.groovy:7)
at org.van.TwoController.index(TwoController.groovy:7)

Again, the defineBeans { ... } mechanism resolved TwoController#twoService. But once we call into TwoService#hello, it throws an NPE because TwoService#threeService is not properly injected.

Integration Test

And here’s the integration test for TwoController:

class TwoControllerIntTestTests {
@Test
void testSomething() {
TwoController twoController = new TwoController()
TwoService twoService = new TwoService()
ThreeService threeService = new ThreeService()
twoService.threeService = threeService
twoController.twoService = twoService
twoController.index()

assertEquals "Expected output", 'Hello from Two:Hello from Three', twoController.response.text
}
}

Summary

Unit tests, with their defineBeans { ... } mechanism, only helps inject one level of beans for the target (i.e. @TestFor()) class. If the injected beans used by that class then have more injected beans of their own, those are not injected correctly.

This works identically for services and controllers.

Integration tests allow for deeper chaining of injected beans. However, the construction/initialization and the actual injection have to be done manually.

Reference

Grails testing guide