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 testing (6)

Wednesday
Dec192012

The future of Web MVC testing

Ok, ok, I'm excited. Right now I just was able to hack my way through a test with Spring 3.2 and the new test web context.

The method I ended up writing looked like this:

@Test
public void tryStartQuiz() throws Exception {
   this.mvc.perform(get("/engine/start/james"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(jsonPath("$.quiz_id").exists());
}

How did I get here?

I upgraded to Spring 3.2, which has native support for loading Web contexts (well, not in a separate-but-parent/child way where you can mount the web context and business context separately, that's coming, but in one context).

Here is the top of my test class:


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
   "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml", 
   "classpath:/META-INF/spring/applicationContext.xml"})
public class EngineControllerTest {

    @Autowired
    WebApplicationContext context;

    MockMvc mvc;

    @Before
    public void setUp() {
        mvc = webAppContextSetup(this.context).build();
    }

    ...

Some analysis...

The MockMvc object, which I referred to as this.mvc in my test, allows me to mock up the web container request and record expectations. It is created in the @Before method, after Spring injects a test WebApplicationContext object, which is driven via the @WebAppConfiguration above.

The WebApplicationContext object is a web app engine in a test engine. It boots a Spring MVC platform, mocking the network engine so that you can submit requests with calls like mvc.perform(get(...) ... and chain assertions, using .andExpect statements to build conditions. It's like headless Spring MVC. This allows you to test by submitting URLs but without having to configure Jetty or Tomcat in a complex server bootstrapped integration test.

But where is it looking for my webapp?

As a nice touch, the default path for the web application is src/main/webapp. If you're not mavenized, or have a test version of your webapp you want to use, you can submit the path to it as the value of the annotation, like:

@WebApplicationContext("src/test/lightweightwebapp")

Assertions and matchers

There are several static imports that make life easier, as outlined in this great slide show from the MVC team (Rossen and friends):

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

I had to hack a bit with the commands and don't have the DSL figured out much so far. But it is nice to test through the request dispatcher rather than inject a controller directly. It's more natural and lets you really test the way production will handle your requests/responses.

What about more sophisticated checks?

The expect syntax doesn't really allow dicing and slicing of the response as easily as grabbing it in a variable. You can tell the mock MVC object to return the result, which contains the MockHttpServletResponse. In this example, I'm using FlexJson (from Roo's dependencies) to deserialize the JSON request into a HashMap of string keys and values, and assert that the data exists in the proper format.

@Test
public void tryStartQuiz() throws Exception {
	JSONDeserializer<HashMap<String, String>> deserializer
			= new JSONDeserializer<HashMap<String, String>>();

	MvcResult result = this.mvc.perform(get("/engine/start/james"))
			.andExpect(status().isOk())
			.andExpect(content().contentType(MediaType.APPLICATION_JSON))
			.andExpect(jsonPath("$.quiz_id").exists())
			.andReturn();

	Map<String, String> jsonPayload =
			deserializer.deserialize(
					result.getResponse().getContentAsString());

	assertThat(jsonPayload.containsKey("quiz_id"), is(true));
	assertThat(jsonPayload.get("nickname"), is("james"));

	System.err.println(jsonPayload);


}

Roo and Spring 3.2

One more thing - I came from Roo 1.2.2, and as I upgraded the spring.version property to Spring 3.2 (yes, it works just fine) so I had to add one dependency in order for this to work, test scoped:

<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>

Roo was just upgraded to version 1.2.3 this week (check it out here). I am hoping it uses Spring 3.2 out of the box.

This test will be checked into the next update of Quizzo-Angular-Roo - my current obsession which is using Spring Roo on the backend, and Angular.JS on the front-end, for a single-page JS quiz engine.

Where to go to view the code?

Today I've pushed this code (somewhat modified) along with working Jasmine tests, AND a working Maven test suite that includes automatic Jasmine testing every time I run mvn test. Check it out at quizzo-angular-roo on GitHub. I warn you, it will be primitive and so far not really doing much, but it will shape up over time.

Sunday
Jul082012

Spock's data tables are sweet!

I'm working with Spock again for updating my CoffeeScript plugin for Roo 1.2.3. There have been a few helpful additions to the add-on APIs (unless I missed them in 1.2.2...) and so my code is getting a tad simpler.

The coolest thing is that converting my tests to Spock, I had an easy way to do a truth table testing condition when I wanted to try out a number of scenarios. Check out this snippet, specifically the "where:" block and the values from the where block variables within the test conditions.

I've switched this test to the more formal given: when: then: syntax, and added the where: section for the data tables settings. I think I was using setup: instead of given: and I also didn't put my expectations in the then: section, which they evaluate in regardless. It's more readable this way.

Note, the version of the coffeescript add-on listed in the test is irrelevant, as the method called to fetch the plugin ignores it but the Plugin constructor requires a version.

@Unroll("evalto #evalto for project available #available, 
         packaging #packaging and pluginList #pluginList")
def "test isPluginInstalled with scenarios"() {
    given:
    def projectOperations = Mock(ProjectOperations.class)
    coffeescriptOperations.projectOperations = projectOperations
    def pom = Mock(Pom.class)

    when:
    def result = coffeescriptOperations.isPluginInstalled()

    then:
    coffeescriptOperations.projectOperations
            .isFocusedProjectAvailable() >> available
    coffeescriptOperations.projectOperations.getFocusedModule() >> pom
    pom.getPackaging() >> packaging
    pom.getBuildPluginsExcludingVersion(_) >> pluginList

    result == evalto

    where:
    pluginList | available | packaging  | evalto
    [] | false | "foo" | false
    [] | true  | "war" | false
    [] | true  | "pom" | false
    [new Plugin("com.theoryinpractise",
            "coffee-maven-plugin", "1.2.0") ] | true | "war" | true
    [new Plugin("com.theoryinpractise",
            "coffee-maven-plugin", "1.2.0")] | true | "pom" | false
}

I was able to fold five specific test cases checking whether the project contains the Coffeescript plugin into one test with five data settings (all false except one):

  • The focused project is not available
  • The packaging is a war, the focused project is available, the add-on is not installed
  • The packaging is a "pom" and the project is available
  • The plugin exists, the type is a war, the focused module is available (happy!)
  • The plugin exists (mounted by hand perhaps) on a pom project which is the current module

I used @Unroll to turn the data table results into five separate tests, each of which listed separately in the test results. Note the method name of the test is comprised of literal strings with data from the datatable columns starting with '#'. Nice, eh?

So I was able to collapse five test scenarios into one with the data tables. Nice.

You can pull the git repository for this add-on by using:

git clone git://git.cloudbees.com/sillyweasel/coffeescript-roo-addon.git
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
}
Thursday
May242012

Spock and Roo - Maven's conventions step in to mess with me

Ok, here's a cautionary tale.

I had everything working just fine in one project using Spock - on Jenkins builds I was getting code coverage working. It was great!

Hey, listen, keep this in mind:

src/test/java is NOT src/main/groovy! Now my jQuery project is starting to use code coverage - go ahead and view the report...

Oh, and one more thing: bind them to test-compile, not test. AAAHH!

:)

Ok, here's my maven build fragment for running the tests (I assume now that the file set is no longer needed...)


<plugin>
    <groupId>org.codehaus.gmaven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.4</version>
    <configuration>
        <providerSelection>1.8</providerSelection>
    </configuration>
    <executions>
        <execution>
			<id>test-run</id>
            <goals>
				<goal>generateTestStubs</goal>
                <goal>testCompile</goal>
            </goals>
			<phase>test-compile</phase>
			<configuration>
				<sources>								
						<fileSet>
							<directory>src/test/groovy</directory>
							<includes>
								<include>**/*.groovy</include>
							</includes>
						</fileSet>							
				</sources>
			</configuration>						
        </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>
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