Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

« Spock and Roo = easier add-on testing, part 1 | Main | Roo add-on development - how to unit test configuration changes »
Wednesday
May162012

Roo Add-On Development, Part the Second - Testing XML Configurations

In the last post, we discussed unit testing Roo add-on code. I feel this is just as vital as testing any other piece of Java code, considering that every time you run the command in the container you literally have to boot it, update the OSGi bundle, and then test. The feedback loop is too long to fix little, annoying bugs like not properly parsing an XML document.

Roo "Advanced add-ons" and Configuration

Let's assume we're not stellar coders. Let's even assume that we aren't the best XML developers. I'm shining a bright line at myself here.

With the CoffeeScript add-on, we want to manipulate the pom.xml file - something we don't need a container to do. Roo uses that good ole' built-in JAXP library (and Apache implementation of course). As Ben Alex would say, "stock standard Java." So, we should be able to easily unit test it.

The CoffeescriptOperationsImpl class - under test!

Last blog we showed you how to test the CoffeescriptCommands object, which delegates calls to the CoffeescriptOperations OSGi bean, which is implemented by the CoffeescriptOperationsImpl class. This is where the add-on's work is being done. So, let's test it.

Setting up the test class and Mockito

Like last time, we need to configure Mockito. We'll assume you've read up on that post and have installed the proper dependencies.

We need to test that our operations code works, and that it calls the proper Roo services. So, let's create our bean under test, and then mock the collaborator:

public class CoffeescriptOperationsImplTest {
  private CoffeescriptOperationsImpl coffeescriptOperations; 

  @Before
  public void setUp() {
    coffeescriptOperations = new CoffeescriptOperationsImpl();
    coffeescriptOperations.projectOperations = 
      Mockito.mock(ProjectOperations.class);
  }
  ...
 

Again, we manually create our class under test, and configure our mocks, in keeping with typical unit tests of components. I had to widen the visibility of the projectOperations reference to 'friendly' access - so that this class, which lives in the same package as the code under test, can see it and replace it with a mock.

h2. Reviewing our method under test - setup()

Let's look at our setup method:

  public void setup(String coffeeDir, 
                  String outputDirectory, boolean bare) {
    String moduleName = projectOperations.getFocusedModuleName();
    Element coffeePluginElement = getCoffeeScriptPluginElement();
    Document document = coffeePluginElement.getOwnerDocument();

    if (bare) {
      addTextElement(document, coffeePluginElement, "bare", "true");
    } else {
      addTextElement(document, coffeePluginElement, "bare", 
        COFFEE_DEFAULT_BARE_SETTING);
    }

    if (coffeeDir != null && coffeeDir.trim().length() > 0) {
      addTextElement(document, coffeePluginElement, "coffeeDir", coffeeDir);
    } else {
      addTextElement(document, coffeePluginElement, "coffeeDir", 
        COFFEE_DEFAULT_SRC_DIRECTORY);
    }

    if (outputDirectory != null && outputDirectory.trim().length() > 0) {
      addTextElement(document, coffeePluginElement, "coffeeOutputDirectory", outputDirectory);
    } else {
      addTextElement(document, coffeePluginElement, "coffeeOutputDirectory", 
        COFFEE_DEFAULT_OUTPUT_DIRECTORY);
    }

    projectOperations.addBuildPlugin(moduleName, new Plugin(coffeePluginElement));
  }

It's clear that we have a LOT of branches in this code, but that's because we're taking input from our command itself. I'll lie here, and tell you that I've written tests against all of these branches, but again, I said I'm lying - and in a further lie, I'll tell you that "I'm gonna get to it!" However, here's why lying doesn't help - I'm sure I have bugs in this code, and I really need to verify it all.

Oh, and I was thinking - I have a few private methods to help me keep the code organized and modular... Perhaps I should test those too but that leads the way of code smell... Interesting read BTW.

Reviewing the tasks in the method

Ok, the method does a few things:

1. Asks a helper method for the Configuration XML file as a basis for the Maven plugin.
2. Does a couple of gyrations so that we can maniuplate the plugin nodes with the DOM API - since Roo's Maven object model is essentially a thin wrapper around the XML API we have to think more in XML. This is something I'll be exploring in the future.
3. Sets the options the user passed in.
4. Adds the build plugin to the Maven build.

Ultimately, though, we need to see if:

1. Given a call to setup(), and the appropriate parameters,
2. Does the Plugin contain the proper information

Our test method for the setup process

Ok,

  @Test
  public void testSetupCoffeescript() {

    when(coffeescriptOperations.projectOperations
       .getFocusedProjectName()).thenReturn("foo");

    // a way for Mockito to grab passed input parameters for testing
    ArgumentCaptor<Plugin> pluginCaptor = 
       ArgumentCaptor.forClass(Plugin.class);

    // invoke our method
    coffeescriptOperations.setup("baz", "bar", false);

    // did we call addBuildPlugin? Also, notice we capture what the
    // method passed to the mocked projectOperations.addBuildPlugin method
    // for the plugin XML Element code
    verify(coffeescriptOperations.projectOperations)
       .addBuildPlugin(any(String.class), pluginCaptor.capture());

    // Since the plugin has been called and we've captured the method's 
    // second argument, we'll pluck it out and take a gander...
    Plugin coffeescriptPlugin = pluginCaptor.getValue();

    // make sure they passed something!
    assertNotNull(coffeescriptPlugin);

    // checks against the model
    Assert.assertEquals("false", coffeescriptPlugin.getConfiguration()
        .getConfiguration().getElementsByTagName("bare")
        .item(0).getTextContent());

    Assert.assertEquals("bar", coffeescriptPlugin.getConfiguration()
        .getConfiguration().getElementsByTagName("coffeeOutputDirectory")
        .item(0).getTextContent());

    Assert.assertEquals("baz", coffeescriptPlugin.getConfiguration()
        .getConfiguration().getElementsByTagName("coffeeDir")
        .item(0).getTextContent());
  }

Mockito's ArgumentCaptor

I guess this is really a testing tools article, rather than a Roo article.

The ArgumentCaptor API is really useful to see what the values were for a mock that was called by your class under test. This is a way to verify that we were passing in the right plugin configuration to our Roo projectManager, which, after all, we aren't testing. That's the Roo team's job!

Wrap-up

Looking at it from a distance, Roo is just a Java platform that generates, manipulates and configures applications. So it can really do anything. However, rather than testing by re-deploying 10 times, we can run a fast Junit test 10 times instead.

If you go to my Silly Weasel link at the top of the blog page, you'll see the OBR URL for getting my Coffeescript, jQuery and (soon) Site add-ons. You can browse my maven repository (the same URL without the repository.xml ending) and grab the source for anything I've released.

Please send me comments if you'd like to add to this discussion.

Best,

Ken

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>