Ken Rimple heads Chariot Solutions' training and mentoring programs, and has developed and/or delivered courseware and seminars in a variety of technologies such as Maven, OSGi, Groovy, Grails and Spring. Throughout his career, Ken has always made it a priority to teach others what he has learned. Ken has served as the technical co-chair of both the Fall Forecast 2008 Cloud Computing Conference and the 2009 - 2012 Emerging Technologies for the Enterprise conferences. He hosts a popular podcast, the Chariot TechCast, and has led or participated in projects written in Java since Java 1.0.2. Ken taught the first Philadelphia-area Sun Introduction to Java course in the late 1990s. He is the co-author (along with Srini Penchikala) of Spring Roo in Action for Manning Publications. He is also an avid photographer and jazz drummer. Ken is a DZone MVB and is not an employee of DZone and has posted 35 posts at DZone. You can read more from them at their website. View Full User Profile

Roo Add-On Development: Testing XML Configurations

05.27.2012
| 2174 views |
  • submit to reddit

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.

 

 

 

 

 

Published at DZone with permission of Ken Rimple, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: