Saturday, January 17, 2009

Testing REST Services with Groovy

For awhile now, RIAs (Rich Internet Applications) have rapidly started replacing traditional server-side web applications (JSP, JSF, etc). Typically, these flashier sites are created using Flex or javascript libraries like extjs or yui. At the heart of these sexy applications live simple REST services that return JSON or XML.

Testing these REST services should be made a high priority for several reasons:

  1. It's the contract between the client and server. Breaking that contract should be avoided.
  2. Your application may not be the only client. Once external parties start consuming your REST services you'll want tests in place to ensure you don't break their clients.
  3. To validate the response is well-formed XML or properly constructed JSON
  4. Valuable black-box test, testing the entire server-side stack starting from the REST layer going all the way down to the DAO layer to the database.
So, what's the easiest way to test REST services? For awhile now I have been combing the internet for the best tools to accomplish this goal, since I wasn't going to do it in pure Java, and I think I finally found the right combination using Groovy and HttpBuilder. Groovy because its super easy to parse XML and JSON, and HttpBuilder because it's a great wrapper for the popular Apache Commons HTTP Client library.

Now let's say you have a REST service at the URL http://localhost:8080/myapp/api/books that returns this JSON:
{"books":[
{"name":"Being Mad","author":"Kit Plummer"},
{"name":"Clown Life","author":"Josh Hoover"},
{"name":"You Miss Me","author":"Ron Alleva"},
{"name":"Talk to me Goose","author":"Jeff Black"}
]}
This is how simple it is to write a test in Groovy using HttpBuilder:
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import static groovyx.net.http.ContentType.JSON

class BooksTest extends GroovyTestCase {
def void test_get_all_books() {
def http = new HTTPBuilder("http://localhost:8080")

http.request(Method.valueOf("GET"), JSON) {
url.path = '/myapp/api/books'

response.success = {resp, json ->
json.books.each { book ->
assert book.name != ""
}
}
}
}
}

To me the major advantage to this approach is being able to traverse the JSON response like I would in javascript. If the response was XML it be just as easy too.

The only two remaining items you would need would be adding the gmaven plugin to your pom and httpbuilder as a dependency.

Wednesday, December 17, 2008

Control versioning in new maven-release-plugin

I have been waiting with much anticipation for the new version of the maven-release-plugin to be, well released. Our team uses this time saving plugin heavily but since moving to a MAJOR.MINOR.PATCH version strategy we quickly found out that it needed some new features. What we really needed was the ability to specify versions for the tag and for the new trunk. For example, when HEAD is at 2.1.0-SNAPSHOT, while in batch-mode the new default version would be 2.1.1-SNAPSHOT, but I want it to be 2.2.0-SNAPSHOT.

Fortunately, someone else was thinking the same thing and the new capabilities where already in the works. Our current version we are using is 2.0-beta-7. But I noticed today the maven team finally released 2.0-beta-8 (see repo1). Here are the release notes for the new version.

What I was specifically interested in was the new batch-mode features. Now instead of having to manually update the POMs after creating a release I can do the following automatically in CI:

mvn release:prepare -DdevelopmentVersion=2.2.0-SNAPSHOT

I haven't tested it yet, but will know in the near future if it works.

Friday, December 5, 2008

Maven Profiles: Something you need to know

An issue came up today in our CI environment and I learned something new about maven profiles. Something that everyone who uses maven should know. Perhaps, like your environment, our project uses maven2 to build and we take advantage of multiple profiles (see our example here). A common use for profiles is to define new modules and properties. For example:

<profiles>
<profile>
<id>integration-tests</id>
<modules>
<module>integration</module>
</modules>
<properties>
<app.port>8080</app.port>
</properties>
</profile>
<profiles>

This profile is activated when you run mvn -P integration-tests install. But sometimes you want a profile on all the time. Maven profiles have the ability to be actived by default. To do this just do the following:
<profiles>
<profile>
<id>integration-tests</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>integration</module>
</modules>
<properties>
<app.port>8080</app.port>
</properties>
</profile>
<profiles>

Then you can just run mvn install and that module and property will already be active.

activeByDefault.....Sometimes
Turns out that perhaps maven should have renamed it to activeByDefaultSometimes. Reading their documentation I learned something important about maven profiles:
"This profile will automatically be active for all builds unless another profile in the same pom is activated using one of the previously described methods. All profiles that are active by default are automatically deactivated when a profile in the pom is activated on the command line or through its activation config."
So in my example, if I have more than one profile defined in my pom and I run that new profile, then my integration-tests profile is deactivated. For example, when I run mvn -P SecondProfile, the module and property defined in the integration-tests profiles are not available to my build. One trick to see which profiles are activated when running maven is to run mvn help:active-profiles.

In general I have never been a huge fan of profiles. They feel clumsy, clutter up the cmd line, and are not well known among your team. As their documentation states, "adding profiles to your build has the potential to break portability for your project". Instead of using Profiles, attach to known phases. For example, see my second example from this previous post. In this example, I use the maven-antrun-plugin to attach to the install phase. Now this will run anytime I run mvn install. No clumsy profile I have to memorize and run. Given that, I am not saying you should avoid Profiles all together. Just use them as a last resort.