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

Entries in Roo in Action (12)

Friday
May252012

Spock and Roo - more complex mocks

Given this method to test:

public boolean isInstalljQueryUICommandAvailable() {

  String jsLocation = pathResolver.getFocusedIdentifier(
      Path.SRC_MAIN_WEBAPP, "/js");
  if (projectOperations.isFocusedProjectAvailable()) {
    boolean isMissingjQueryUI = fileManager.findMatchingAntPath(
        jsLocation + "/jquery-ui-*.min.js").isEmpty();
    return !isInstalljQueryCommandAvailable() && isMissingjQueryUI;
  } else {
    return false;
  }
}

We have several challenges here:

  • We are calling the isInstalljQueryCommandAvailable() method from my prior post, so we need to mock the code in that invocation
  • We are going to call the same methods with different results
  • We have to mock a non-empty call to the findMatchingAntPath

To refer you to the prior method, here it is:

public boolean isInstalljQueryCommandAvailable() {
  String jsLocation = pathResolver.getFocusedIdentifier(
      Path.SRC_MAIN_WEBAPP, "/js");

  return fileManager.findMatchingAntPath(
      jsLocation + "**/jquery-1.*.min.js").isEmpty();
}

Setting up our test mocks

The test setup needs to take those conditions into account. First, we define our test method and fill in what we expect to happen:

def "isJqueryUIInstallAvailable called and happy path"() {

     setup:

     when:
     def result = operations.isInstalljQueryUICommandAvailable()

     then:
     result == true
}

The setup

Ok, now let's define our setup block. We'll need to return a simulated search result for our invocation of the pathResolver.findMatchingAntPath method in the jquery availability check - we want to state that we already have jQuery, but not jQuery UI, in our search path. So, let's create a simulated file details object first:

setup:
FileDetails fd = new FileDetails(new File("foo"), 234L);
SortedSet<FileDetails> fileDetailsSet = new TreeSet<FileDetails>()
fileDetailsSet.add(fd)

Now, we'll start by defining our mock conditions for something where the return value won't vary: the getFocusedIdentifier method of the pathResolver. Because we're just mocking it anyway, we don't really care what we return. This is true in both cases where it is called. So, we'll just return a junk value, but do it twice so that we can expect it to be called two times, and return the same result:

2* operations.pathResolver.getFocusedIdentifier(_, _) >> "foo"

So far, so good. Next, we'll mock a call to the isFocusedProjectAvailable() method of projectOperations. Hey, I noticed I do this in the UI setup, but not in the jQuery API setup, so I found a bug this way! So, it's two invocations, and I added the single invocation to the other test methods for the jQuery API setup too. Yay, team!:

2* operations.projectOperations.isFocusedProjectAvailable() >> true

Next, the more difficult one. I want to make sure we find the jQuery API JS file in the search, but NOT the jQueryUI API. In other words, I want to make sure we have jQuery but NOT jQueryUI, so that we can then allow the user to install jQueryUI.

2* operations.fileManager.findMatchingAntPath(_) >>>
                [new TreeSet<FileDetails>(), fileDetailsSet]

Wait, what? Ok, the syntax goes like this: we want two calls to the findMatchingAntPath method. The triple greater-than signs state that each invocation will return a different value. The first time, we'll return an empty TreeSet, which is the contract the method provides if no search result is found. The second time, we'll return a mocked set of file details, with our bogus one inside (we're then calling the one for jQuery, not jQueryUI, and we have to fake out that we have something.

The full test looks like this:

def "isJqueryUIInstallAvailable called and happy path"() {

     setup:
     FileDetails fd = new FileDetails(new File("foo"), 234L);
     SortedSet<FileDetails> fileDetailsSet = new TreeSet<FileDetails>()
     fileDetailsSet.add(fd)

     2* operations.pathResolver.getFocusedIdentifier(_, _) >> "foo"
     2* operations.projectOperations.isFocusedProjectAvailable() >> true
     2* operations.fileManager.findMatchingAntPath(_) >>>
             [new TreeSet<FileDetails>(), fileDetailsSet]

     when:
     def result = operations.isInstalljQueryUICommandAvailable()

     then:
     result == true
}
Tuesday
May222012

Spock and Roo = easier add-on testing, part 1

Hey all...

I've been working on my add-ons. If you're reading this from my website, head over to the Silly Weasel menu and get the repository OBR to install my Spock add-on when trying this.

You are running Groovy in Roo? Shame on you!

Hey, before I was a Roo in Action author, I was a huge fan of Grails. I still am, because Groovy + your favorite APIs is so much more concise to program against than with stock Java. So, one of my backburner projects was getting Groovy to work on Roo.

Roo doesn't like Groovy (yet)

It turns out the reason is AspectJ. Right now, our projects are all compiling against the AspectJ API (version 1.2.x if memory serves). The problem is that Groovy's bytecode is not compatible with AspectJ, at least in terms of getting it to compile in the same Maven compiler step. I looked on the AspectJ timeline, and saw AspectJ 1.5 and 2.0 will support Groovy as a target for weaving. Coolness!

But that doesn't stop us from using it to test!

Yep, that's right. Turns out you can attach the Groovy compiler to your test phase, and it works. By the time your tests are compiling, the main classes have been compiled and weaved with that AspectJ goodness. So your ITD methods are there, and you can use any old testing tool you want to test 'em.

Note, I think you may have issues with my approach, if you are going to use the DataOnDemand testing framework, but so far, it seems like it works well.

My example for the evening - testing with mocks for Add-on Commands

Ok, here's a practical example. My jQueryUI add-on command class delegates the isAvailable and install commands to the Operations Implementation class, JqueryuiOperationsImpl. I wanted to start working up the test coverage for this add-on, because right now I am failing my hudson build due to a lack of test coverage.

So, here is a Groovy Spock specification, which I named JQueryuiCommandsTest.groovy and placed in src/test/groovy (after installing the maven script I'll reference below it). I've even created a Spock Roo add-on for you to use to set this up in your own projects.



package org.sillyweasel.addons.jqueryui

class JQueryuiCommandsTest extends spock.lang.Specification {

    JqueryuiCommands commands
    JqueryuiOperations operations

    def setup() {
        operations = Mock()
        commands = new JqueryuiCommands();
        commands.operations = operations;
    }

    def "when calling availability we defer to implementation" () {
        when:
        boolean result = commands.isApiInstallCommandAvailable()

        then:
        1*operations.isInstalljQueryCommandAvailable()
    }

    def "when calling ui availability we defer to implementation" () {
        when:
        boolean result = commands.isInstalljQueryUIAvailable()

        then:
        1*operations.isInstalljQueryUICommandAvailable()
    }

    def "when calling installjQueryApi we defer to jquery operations method" () {
        when:
        commands.installjQueryApi()

        then:
        1*operations.installjQueryApi();
    }

    def "when calling installjQueryUI we defer to jquery operations method"() {
        when:
        commands.installjQueryUIApi()

        then:
        1*operations.installjQueryUIApi();
    }


}

How did you get this to work?

I basically stole the configuration for Spock and Maven, and tweaked it a bit. Here are the relevant bits of my Maven build:

<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>0.6-groovy-1.8</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-spring</artifactId>
    <version>0.6-groovy-1.8</version>
    <scope>test</scope>
</dependency>

...

<plugin>
   <groupId>org.codehaus.gmaven</groupId>
   <artifactId>gmaven-plugin</artifactId>
   <version>1.4</version>
   <configuration>
       <providerSelection>1.8</providerSelection>
       <source>src/test/groovy</source>
   </configuration>
   <executions>
       <execution>
           <goals>
               <goal>testCompile</goal>
           </goals>
       </execution>
   </executions>
   <dependencies>
       <dependency>
           <groupId>org.codehaus.gmaven.runtime</groupId>
           <artifactId>gmaven-runtime-1.7</artifactId>
           <version>1.3</version>
           <exclusions>
               <exclusion>
                   <groupId>org.codehaus.groovy</groupId>
                   <artifactId>groovy-all</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
       <dependency>
           <groupId>org.codehaus.groovy</groupId>
           <artifactId>groovy-all</artifactId>
           <version>1.8.6</version>
       </dependency>
       <dependency>
           <groupId>org.spockframework</groupId>
           <artifactId>spock-core</artifactId>
           <version>0.6-groovy-1.8</version>
       </dependency>
       <dependency>
           <groupId>org.spockframework</groupId>
           <artifactId>spock-spring</artifactId>
           <version>0.6-groovy-1.8</version>
       </dependency>
   </dependencies>
</plugin>

Thursday
May102012

Roo add-on development - how to unit test configuration changes

I'm working on updates to several Roo add-ons, which I am going to be pushing out to the Roo repository soon. Here are some challenges and how I overcame them.

The add-on command marker interface

This add-on sets up Coffeescript using a Maven plugin. Here is the add-on source:

package org.sillyweasel.addons.coffeescript;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;

@Component
@Service
public class CoffeescriptCommands implements CommandMarker { 

  /**
   * Get a reference to the CoffeescriptOperations from the underlying OSGi
   * container
   */
  @Reference
  CoffeescriptOperations operations;

  @CliAvailabilityIndicator({"coffeescript setup"})
  public boolean isSetupCommandAvailable() {
    return operations.isSetupCommandAvailable();
  }

  @CliAvailabilityIndicator({"coffeescript remove", "coffeescript addjoinset", 
    "coffeescript removejoinset", 
    "coffeescript listjoinsets"})
  public boolean isCoffeescriptInstalled() {
    return operations.isPluginInstalled();
  }

  @CliCommand(value = "coffeescript setup", 
     help = "Install the CoffeeScript compiler")
  public void setup(
    @CliOption(key = "outputDirectory", mandatory = false,
        unspecifiedDefaultValue = "${project.build.directory}") String outputDirectory,
    @CliOption(key = "coffeeDir", mandatory = false,
        unspecifiedDefaultValue = "src/main/webapp/scripts",
        specifiedDefaultValue = "src/main/webapp/scripts") String coffeeDir,
    @CliOption(key = "bare", mandatory = false,
        unspecifiedDefaultValue = "false",
        specifiedDefaultValue = "true") boolean bare) {

    operations.setup(coffeeDir, outputDirectory, bare);
  }

  public void addJoinSet(
      @CliOption(key = "joinSetId", mandatory = true, specifiedDefaultValue = "main")
      String joinSetId,
      @CliOption(key = "includes", mandatory = true, 
         help = "comma-separated list of search paths for javascript files to include")
      String includes,
      @CliOption(key = "excludes", mandatory = true, 
         help = "comma-separated list of search paths for javascript files to exclude")
      String excludes) {

    // TODO - set up the command

  }

  @CliCommand(value = "coffeescript remove", help = "Remove the coffeescript compiler")
  public void remove() {
    operations.remove();
  }
}

Some of this I haven't written yet (hence the TODOs) but the rest is fleshed out and I'll be going over it later.

Task #1 - Roo addons don't include JUnit?

Right now, no. (ROO-3161) However, you can just add it yerself. I'm using Mockito for mocking (more about that later) as well as messing around with those lovely Hamcrest matchers:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.10</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-all</artifactId>
  <version>1.1</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.9.0</version>
  <scope>test</scope>
</dependency>

Task #2 - You need to widen the visibility of the Roo-injected OSGi objects to mock them

Because we want to unit test our add-ons without firing up an OSGi container, we will need to mock things. I'm using Mockito, an excellent mock and spy library. But I can't get to the injected objects, and the test I'm going to show here has to make a "mockery" of the ProjectOperations Roo service. It is defined this way in the code of the Operations implementation class:

private @Reference ProjectOperations projectOperations;

The problem with this is that I can't just use Mockito's mocking method to access and fake it out. So, I widened to friendly scope, which allows access from the same package. I guess the better way would maybe have been to create a getter but then I'm exposing it in a wider range than just the add-on's specific package:

@Reference ProjectOperations projectOperations;

Task #3 - how to test the Command Marker

Ok, so the first thing I want to do is make sure my Command Marker calls the proper command implementation methods. First up, we widen the reference to our CoffeescriptOperations variable:

@Reference CoffeescriptOperations operations;

Then, we write a method that uses Mockito to mock calls to the operations object. This is the test class:

package org.sillyweasel.addons.coffeescript;

import junit.framework.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;

public class CoffeescriptCommandsTest {

  private CoffeescriptCommands commands;

  @Before
  public void setUp() {
    commands = new CoffeescriptCommands();
    commands.operations = mock(CoffeescriptOperations.class);
  }

  @Test
  public void testIsPluginInstalled() {
    when(commands.operations.isPluginInstalled()).thenReturn(false);
    assertThat(commands.isCoffeescriptInstalled(), is(false));
  }

  @Test
  public void testIsCoffeescriptSetupAvailable() {
    when(commands.operations.isSetupCommandAvailable()).thenReturn(true);
    assertThat(commands.isSetupCommandAvailable(), is(true));
  }

  @Test
  public void testIsCoffeescriptRemoveAvailable() {
    when(commands.operations.isPluginInstalled()).thenReturn(true);
    assertThat(commands.isCoffeescriptInstalled(), is(true));
    verify(commands.operations, times(1)).isPluginInstalled();
  }

  @Test
  public void testInstallCoffeeScript() {
    commands.setup("foo", "bar", true);
    verify(commands.operations).setup("foo", "bar", true);
  }

  @Test
  public void testRemoveCoffeeScript() {
    commands.remove();
  }

  @Test
  @Ignore
  public void testFileSets() {
    Assert.fail();
  }
}

See how I'm using some Hamcrest matchers in there? I like assertThat(..., is()) syntax.

So now we know that our command marker is actually calling the delegate methods when it is being invoked. And we're on our way, able to mock anything we want to, provided we widen the scope a bit from a 100% internal private member variable.

In our next post I'll show you how to test the actual XML configuration. That one is going to be rather large so I'll separate it into another one.

Sunday
May062012

Don't call names - Roo's Tag with a tag problem

Eureka, ish..

I was trying to find time to debug a problem that's been on my backlog for a while, and someone else ran into it. A reader over on the Manning Spring Roo in Action forum was getting hit with a 400 invalid request error when trying one of the samples from the book (the many-to-many example).

A while back, I ran into this problem - my m:m arrangement was between Courses and Tags, and the field name of the actual tag in the Tag entity was called 'tag'. When I did this, I got the same error. Someone hit it in the forums, we renamed it to 'name' and committed our code, and I forgot about it for a while.

My theory was that the type converters somehow were generated wrongly. Nope, I have a " target="_blank" class="offsite-link-inline">JUnit test to review that does it properly, so that's not the problem.

Actually, the problem is with the type conversion, but it's not the fault of the Roo code for type converters (to my knowledge so far). They are just doing what they are supposed to do.

The REAL Problem

First of all, my theory on the bad type converters was wrong. I proved it by writing a set of tests to make sure I a) understood the true workings of the generated type converters and b) that they worked. In the next edition of Roo in Action I think I need to devote some time to these. See the attached sample at the end to see my tests. Here is how they work (example - the tag):

Type Conversions

  • Convert an object -> a single line String representation as a default label for a given dropdown entry. (converterFactory.getTagToStringConverter())
  • Hydrate a Tag via a String representation of the key (converterFactory.getStringToTagConverter())
  • Hydrate a Tag via a primary key value (converterFactory.getIdToTagConverter())

All of these work just fine when tested individually, which they should.

The Tag to String converter is used in dropdowns for the many-to-many relationship, so that when the tag editing screen (which owns the relationship) allows a user to select one or more courses, it just shows the field values separated by spaces.

So, now what?

Spring MVC creates forms with a set of assumptions, including the fact that the form will be of a certain bean instance root. This is provided by specifying the modelAttribute in Spring MVC, but since Roo does this for us, we aren't in charge of that in a scaffolded UI.

Here is the create.jspx file for reference.

The scaffolded value is generated by create.tagx, which uses this code:

<form:form action="${form_url}" method="POST"
   modelAttribute="${modelAttribute}" enctype="${enctype}" 
   onsubmit="${jsCall}"> ...

The essential problem I think we're running into with the samples the way they are put together is:

  • The "bean context" / model attribute name of the form being submitted is expected to be 'tag'
  • The form is submitting fields in this form with names such as description, id, and also tag
  • Spring MVC is spitting nails on the fact that you have tag.tag in your request. It wants to convert the nested tag into another Tag object, by hydrating it (probably to support recursive relationships or something). Well, it takes your String for the tag label and tries to take it through idToTagConverter() which fails on a numeric parse.

How to fix For now, avoid using the entity name as a field name in a given entity. I need to do more extensive testing to see why this is happening. This is something that I should know by heart, I guess, but I've been wrestling with other areas of Java and Roo and haven't been as connected to the web forms as I used to be.

If anyone wants to take this on, I have posted the code sample in the original Manning thread (look on the second page) with the junit tests included. Just create a course, then create a tag with the 'tag' field of any alpha value.

[b]The sample test[/b]

I created a spring junit test to check my assumptions (I imported hamcrest-all for matching support to make the assertions readable quickly, the pom includes org.hamcrest:hamcrest-all:1.1).

Sunday
Sep042011

Skipping the PGP Signing Process 

If you need to install or deploy the maven artifact of your add-on to an internal repository server, and you don't need to deploy to a public OBR repository such as the RooBot server, you can choose to disable the PGP key signing process. Just comment out the maven-gpg-plugin entry in your project's pom.xml file, and you can then use the mvn install and mvn deploy commands internally:

<!-- comment out this block temporarily
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-gpg-plugin</artifactId>
  <version>1.3</version>
  <executions>
    <execution>
      <id>sign-artifacts</id>
      <phase>verify</phase>
      <goals>
        <goal>sign</goal>
      </goals>
    </execution>
  </executions>
</plugin>
-->

We suggest following the signing process anyway, as it verifies that a deployed maven artifact came from a given, trustable source. However, for internal testing and for non-critical applications, especially internal ones, disabling this check can save your some configuration headaches.