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

Grails run-script with parameters

Grails, programming

Scripts vs. parameters

The “grails run-script” task allows running a bunch of scripts, and therein lies the problem: the parameters passed to the command are interpreted by the command to be names of scripts to run:

grails run-script ~/script1.groovy ~/script2.groovy ~/script3.groovy

This works fine if the scripts are self-contained and did not expect any parameters on their own.  What if ~/script1.groovy expects some parameters?

The problem is that the RunScript.groovy script assumes each parameter to be script files to run, so there is no support for parameters to any of the script files run.

So I would like to trade:

  • the ability to run multiple scripts with one call to run-script

with

  • multiple calls to something like run-script-with-params

I figured that, if I were to be calling scripts in a batch, making multiple calls shouldn’t be a big deal. Not being able to pass parameters to individual scripts, on the other hand, IS a big deal.

A tweak of the script RunScript.groovy (below) can help with that:

UPDATE: I just found that a similar script (even the name is remarkably similar) has already been suggested here.  (Scroll down the comment section to see the suggestion.)  Oh well.  Wish it was added to Grails so I didn’t have to spend so much time on this.

import org.springframework.orm.hibernate3.SessionFactoryUtils
import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager

/**
* GANT script to run an arbitrary Groovy script with GORM and Spring context and also with parameters
*
* A sample invocation of a script:
* grails run-script-with-params ~/script1.groovy -enableLog -name=van
*
* Arguments are processed as follows:
* --xx=yy or -xx=yy are parsed into key/value pairs added into argsMap and passed to the script
* --xx or -xx are interpreted as xx:true and treated as a key/value pair and added into argsMap
* xxxxx are treated as a parameter and stored into the array argsMap.params
*
* The FIRST parameter is assumed to be the path to the script file to run.
* The rest of the parameters will be parsed into a map passed onto the script as argsMap
*
* This script is based on and inspired by
* {@link RunScript}
*/
includeTargets << grailsScript("_GrailsBootstrap")

target(main: "GANT script to run an arbitrary Groovy script with GORM and Spring context") {
depends checkVersion, configureProxy, bootstrap, runScript

}

target(runScript: 'Runs a Groovy script file with the parameters propagated to it') {
configureHibernateSession()

if (!argsMap.params) {
event('StatusError', ['ERROR: Required script name parameter is missing'])
System.exit 1
}
executeScriptWithArgsMap argsMap.params[0], classLoader, argsMap
}

def executeScriptWithArgsMap(scriptFile, classLoader, argsMap) {
File script = new File(scriptFile)
if (!script.exists()) {
event('StatusError', ["ERROR: Script $scriptFile does not exist"])
System.exit 1
}
event('StatusUpdate', ["Running script $scriptFile ..."])
def persistenceInterceptor = appCtx.containsBean('persistenceInterceptor') ? appCtx.persistenceInterceptor : null
persistenceInterceptor?.init()
try {
def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)
shell.evaluate script.text
event('StatusUpdate', ["Script $scriptFile complete!"])
} finally {
persistenceInterceptor?.flush()
persistenceInterceptor?.destroy()
}
}

def configureHibernateSession() {
// bind a Hibernate Session to avoid lazy initialization exceptions
TransactionSynchronizationManager.bindResource(appCtx.sessionFactory,
new SessionHolder(SessionFactoryUtils.getSession(appCtx.sessionFactory, true)))
}

setDefaultTarget(main)

The trick is how we start a Groovy shell, passing it a Binding object:

def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)

We added argsMap to the Binding object passed to the shell. And this allows the target script to now have access to argsMap, in addition to the already available ctx (the Spring application context) and grailsApplication (the GrailsApplication object).

Put the script above into the Grails project’s script subdir as /scripts/RunScriptWithParams.groovy and we can now run this from the Grails subdir:

grails run-script-with-params ~/script1.groovy --enableThis --param1=val1

The target script (in this case ~/script1.groovy) will be able to get the parameters via the argsMap global variable injected into the shell:

println "enableThis = ${argsMap.enableThis}, param1 = ${argsMap.param1}"

Windows caveat

The only caveat I found is that, when running in the Windows command prompt, parameters with a format --key=value need to be in quotes:

grails run-script-with-params ~/script1.groovy --enableThis "--param1=val1"

Running under Cygwin’s terminal works fine without the quotes, however.  So it’s something special about the Windows command.exe or cmd.exe.

Grails class names

Grails, programming

Seems like there are many flavors of the name for a Grails class:

  • fullName — Returns the full name of the class in the application with the the trailing convention part and with the package name
  • name — Returns the logical name of the class in the application without the trailing convention part if applicable and without the package name.
  • naturalName — Returns the name of the property in natural terms (eg. ‘lastName’ becomes ‘Last Name’)
  • shortName — Returns the short name of the class without package prefix
  • propertyName — Returns the name of the class as a property name
  • logicalPropertyName — Returns the logical name of the class as a property name

These are from http://grails.org/doc/2.2.x/api/org/codehaus/groovy/grails/commons/GrailsClass.html.  It looks like somebody just typed in something just to have something show up in Javadoc.

A pause in the code to show an example is probably the best way to document these names.  For a class like com.blah.UserManagementController:

  • fullName: com.blah.UserManagementController
  • name: UserManagement
  • naturalName: User Management Controller
  • shortName: UserManagementController
  • propertyName: userManagementController
  • logicalPropertyName: userManagement

Now that that’s out of the way, notes:

Spring’s application context (e.g. grailsApplication.mainContext) holds on to its beans using names that may not be obvious. The easiest way to figure out is to fire up

grails console

and evaluate

grailsApplication.mainContext.getBeanDefinitionNames()

and find the bean’s name.

Mobile-enabling a Web page

android, mobile, programming

Stop that condensation

By default, my mobile Web browser attempts to make everything smaller so that a “normal” web page will fit in the phone’s screen, even when the page has a few items that would fit in the mobile browser’s native resolution.

One way I found (that this WordPress page is doing) to prevent that over-eager condensation of the page is adding a meta tag:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />

See the viewport page from Apple.