or, “how I fell into a deep morass of version discrepancies”
Something I am working on at the moment is rolling in use of DynamoDB Local for integration tests. Now one thing I’ve noticed is that Amazon aren’t really drinking the Maven kool-aid. This generally isn’t a huge problem, as mostly it’s just a matter of getting artifacts into our repository and setting them up as dependencies. DynamoDB Local is a bit different though.
At it’s heart, DynamoDB Local is a wrapper around an SQLite instance, with the DynamoDB API bolted on in front of it. This is a good thing, as it means that by using DynamoDB Local instead of some other mocking framework (specifically Alternator) is a better guarantee that the mocked target is behaving in the way that the real DynamoDB will. Don’t get me wrong, Alternator is a good solution, and has nice light-weight semantics that should serve pretty well anyone, but I did find at least one test that was passing with Alternator that would not pass when pointed at DynamoDB itself. Fortunately it was not in code that was in production use yet, but…
The difficulty with DynamoDB Local is that, as a wrapper around SQLite, it needs to be running while the tests are running, which is not entirely simple in Maven. Fortunately someone has already rolled out a nice little Maven plugin to take care of starting and stopping the instance (a big thank you to Yegor Bugayenko for some nice work). Getting this working from the project documentation was fairly straightforward, however the intention of that documentation was to run DynamoDB Local for integration, rather than unit tests. That’s fair enough, but I’m trying to reduce the number of integration tests in favour of unit tests, as that makes them easier to measure and monitor using Jacoco (and to a lesser extent Sonar). To that end, I’ve modified his instructions to get it working for unit testing.
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.XXXX</groupId>
<artifactId>YYYY</artifactId>
<version>1.14.2</version>
</parent>
<artifactId>ZZZZ</artifactId>
<version>1.8.0-SNAPSHOT</version>
<name>ZZZZ</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dynamodblocal.tgz>${com.jcabi:DynamoDBLocal:tgz}</dynamodblocal.tgz>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.6.1</version>
</dependency>
<!-- ref http://www.jcabi.com/jcabi-dynamodb-maven-plugin/usage.html -->
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>DynamoDBLocal</artifactId>
<version>2013-09-12</version>
<type>tgz</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<!-- allocate a port for dynamodblocal -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>generate-test-resources</phase>
<goals>
<goal>reserve-network-port</goal>
</goals>
<configuration>
<portNames>
<portName>dynamodblocal.port</portName>
</portNames>
</configuration>
</execution>
</executions>
</plugin>
<!-- force the dynamodb TGZ to be obtained -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-dynamodb-maven-plugin</artifactId>
<version>0.2</version>
<configuration>
<port>${dynamodblocal.port}</port>
<tgz>${dynamodblocal.tgz}</tgz>
</configuration>
<executions>
<!-- start just before unit tests -->
<execution>
<id>beforetests</id>
<phase>generate-test-resources</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<!-- make the port available during unit tests -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<dynamodb.port>${dynamodblocal.port}</dynamodb.port>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>DEV</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-dynamodb-maven-plugin</artifactId>
<version>0.2</version>
<configuration>
<port>${dynamodblocal.port}</port>
<tgz>${dynamodblocal.tgz}</tgz>
</configuration>
<executions>
<!-- stop after unit tests - this requires us to at least do a verify -->
<execution>
<id>aftertests</id>
<phase>package</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>JENKINS</id>
<build>
<plugins>
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-dynamodb-maven-plugin</artifactId>
<version>0.2</version>
<configuration>
<port>${dynamodblocal.port}</port>
<tgz>${dynamodblocal.tgz}</tgz>
</configuration>
<executions>
<!-- stop after unit tests - this requires us to at least do a verify -->
<execution>
<id>aftertests</id>
<phase>deploy</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
There’s one additional weirdness about the solution above that needs explaining. We are using Jenkins for CI builds, and the target run by CI is slightly different to the target run by developers on their desktop. By mucking about with profiles, I can issue the ‘stop’ directive to halt the DynamoDB Local instance at different points in the lifecycle. Note that if the developer does not run mvn verify
(which is the mandated minimum requirement before check-in), then the DynamoDB Local instance keeps running after Maven terminates and needs to be manually clobbered. On Jenkins, I issue the stop directive at a fairly arbitrary point after the integration tests. The Jenkins build never actually fires this ‘stop’ directive, however the way in which Jenkins runs tests means that Jenkins itself clobbers the DynamoDB instance when the build terminates.
Some caveats:
- This works for my particular requirements, your mileage may vary;
- The plugin throws a warning message and stack trace when ‘stop’ is invoked, this is a known issue, and the instance does stop correctly;
- As a result of that issue, a new version of the plugin has been built, and there could be other required changes to the POM;