Thursday 7 April 2011

Techie Books Suggestions

(Or "How I Learned To Stop Worrying About Gramatically Correct Blog Titles")

I'm starting to add to the dev side of my techie library which, as you can appreciate, is pretty biased toward sysadmin stuff at the moment (DNS, firewall, exim, Jabber and so on).

So far I've added the two GinAs, and recently jQuery in Action, Javascript: The Good Bits, Javascript: The Definitive Guide and The Java Developer's Guide to Eclipse.

So what else should I have on my wish list? Freely downloadable is good too - it doesn't have to be expensive dead-tree format - especially if it can be Kindleised.

Would love to hear your thoughts and suggestions.

Tuesday 22 February 2011

Remote Controlled Geb Functional Tests

I've just started on a new project where Geb and Spock are our main functional testing tools. They're nice tools to work with but sometimes it's hard to write tests quickly when you can't see what's going on. I want to be able to step through tests like in Selenium IDE but without all the shortcomings of using fixture controllers.

One way to develop functional tests under Grails is to keep the app running while you launch tests from another jvm and use -baseUrl to target the remote jvm. You can do this from the command line or use the Functional Test Development plugin. Another way is to keep the app running and run the tests from within an IDE like IntelliJ; a bonus is that you can run individual tests/features which is great if you have a fairly large spec.

Whichever way you choose, you're going to need to setup data on the target jvm. On my last project we used fixture controllers that we would call from our functional tests but these grew into thousands of lines because re-use after a while became difficult. A better way to do it is to bundle the test data with the actual test which is what the Remote Control plugin allows us to do - the plugin documentation has a better explanation of what I mean.

With all that said, what I want to be able to do is:
  • Run my app up
  • Create my data in my setup block as though I was in an integration test.
  • Right click on the test from intellij and hit run (or use debug and step through line by line)
Take a look at the plugin docs, it's pretty straight forward but we had to override getBaseUrl and create our own BuildSettingsHolder because remote control doesn't like running outside of a grails command line. So our parent Spec looked like:
public ParentSpec extends GebSpec {
String getBaseUrl() {
"http://localhost:8080/monkeytails/"
}

def setupSpec() {

if (BuildSettingsHolder.getSettings() == null) {
BuildSettings bs = new BuildSettings()
bs.metaClass.getFunctionalTestBaseUrl = { getBaseUrl() }

BuildSettingsHolder.setSettings(bs)
}
}

def cleanupSpec() {
resetDatabase()
}

void resetDatabase() {
def remote = new RemoteControl()
remote {
User.list().each{ it.delete(flush:true) }
// Other teardown stuff goes here
// ...
return true
}
}
}
and then your tests:
    def "Test something"() {

setup:
def remote = new RemoteControl()
remote {
User user = new User()
user.save(flush: true)
return true //have to return something serializable
}
.
.
}
It's handy to have firebug in the firefox that webdriver opens so use the profile manager to create a profile called test and then change your IntelliJ JUnit Run configuration VM Parameters to include '-Dgeb.driver=firefox -Dwebdriver.firefox.profile=test'

Now you when you right click and run your test, IntelliJ will first compile everything and hopefully run your test. I say hopefully because I have a bunch of inline plugins so I have to exclude them from the compile path and hope for the best. Setting breakpoints and choosing debug lets you step through your test and let you can use firebug within the browser.

There's a few things to be aware of. Groovy's property setters don't seem to work so you can't do stuff like user.username, instead you have to use the setter or if there isn't one you can use setProperty(). Spread operators don't work too well either, so you can't do User.list()*.delete().

Update: forgot to mention that you should run the app in test mode or Remote Control will not set up its listener unlesss you set remoteControl.enabled to true in your app config