How do I add JAR files to the test archive?

A fairly common requirement is to add JAR files (libraries) to the test archive. There is an open feature request (ARQ-66) to add this capability to Arquilian natively. This FAQ will explain how you can accomplish this requirement in the interim. The instructions are specific to a Maven project, but the concept can be extended for other types of projects as well (Gradle, etc).

 

Caching remote dependencies

 

If your project is using Maven, the testCompile step will resolve any test dependencies and put them in your local Maven repository. By the time the test executes, you can be sure that the JAR files you need will be in your local repository. That allows us to simply reference a file in a central place on the local filesystem to include it into the test archive.

 

Resolving cached dependencies

 

Here's a fairly crude resolver that converts a Maven artifact reference into a java.io.File object (also supports a single argument shorthand notation that can resolve one or more artifacts).

 

public class MavenArtifactResolver
{
   private static final String LOCAL_MAVEN_REPO =
         System.getProperty("maven.repo.local") != null ?
               System.getProperty("maven.repo.local") :
               (System.getProperty("user.home") + File.separatorChar +
               ".m2" + File.separatorChar + "repository");

   public static File resolve(final String groupId, final String artifactId,
      final String version)
   {
      return resolve(groupId, artifactId, version, null);
   }

   public static File resolve(final String groupId, final String artifactId,
      final String version, final String classifier)
   {
      return new File(LOCAL_MAVEN_REPO + File.separatorChar +
            groupId.replace(".", File.separator) + File.separatorChar +
            artifactId + File.separatorChar +
            version + File.separatorChar +
            artifactId + "-" + version +
            (classifier != null ? ("-" + classifier) : "") + ".jar");
   }

   public static File resolve(final String qualifiedArtifactId)
   {
      String[] segments = qualifiedArtifactId.split(":");
      if (segments.length == 3)
      {
         return resolve(segments[0], segments[1], segments[2]);
      }
      else if (segments.length == 4)
      {
         return resolve(segments[0], segments[1], segments[2], segments[3]);
      }
      throw new IllegalArgumentException("Invalid qualified artifactId syntax: " +
         qualifiedArtifactId);
   }

   public static File[] resolve(final String... qualifiedArtifactIds)
   {
      int n = qualifiedArtifactIds.length;
      File[] artifacts = new File[n];
      for (int i = 0; i < n; i++)
      {
         artifacts[i] = resolve(qualifiedArtifactIds[i]);
      }

      return artifacts;
   }
}

 

If you are using an alternative location for your local Maven repository, controlled using the maven.repo.local Maven property, you will need to promote that property as a Java system property in the surefire plugin configuration:

 

<plugin>       
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.4.3</version>
   <configuration>
      <systemProperties>
         <property>
            <name>maven.repo.local</name>
            <value>${maven.repo.local}</value>
         </property>
      </systemProperties>
   </configuration>
</plugin>

 

 

Otherwise, the default location will be used.

 

Including dependency artifacts

 

You'll now add the required JAR files into your archive in the Arquillian @Deployment method. Let's assume that we need to add commons-logging and commons-lang.

 

When you need to add a JAR file to your artifact, you should use an artifact that is a library container, such as a WAR or an EAR (though you could also use Archive#merge() to dump the classes into a JavaArchive). In this case, we'll choose a WAR. Notice that I've added the beans.xml to the WEB-INF directory, which is required if I want to use CDI in a WAR deployment.

 

 

@Deployment
public static Archive<?> createTestArchive()
{
   WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war")
         .addClasses(...)
         .addAsLibraries(MavenArtifactResolver.resolve(
            "commons-logging:commons-logging:1.1.1",
            "commons-lang:commons-lang:2.5",
            "org.testng:testng:5.10:jdk15"
         ))
         .addAsWebResource(EmptyAsset.INSTANCE, "beans.xml");
   // verify that the JAR files ended up in the WAR
   System.out.println(war.toString(true));
   return war;
}

 

Using staged artifacts

 

If you want to be able to leverage the metadata in your POM, such as the versions defined in the dependencyManagement section or the dependencies associated with a particular scope, you can use the Maven dependency plugin to copy the artifacts into a directory under target during the process-test-resources phase. Then you can reference those artifacts from your test case.

 

Here's the plugin configuration to that hand picks JAR files that you'll need in the tests:

 

 

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-dependency-plugin</artifactId>
   <version>2.1</version>
   <executions>
      <execution>
         <id>copy-test-libs</id>
         <phase>process-test-resources</phase>
         <configuration>
            <artifactItems>
               <artifactItem>
                  <groupId>commons-lang</groupId>
                  <artifactId>commons-lang</artifactId>
                  <!-- version defined in the dependencyManagement section -->
                  <overwrite>false</overwrite>
               </artifactItem>
            </artifactItems>
            <outputDirectory>
               ${project.build.directory}/test-libs
            </outputDirectory>
            <stripVersion>true</stripVersion>
         </configuration>
         <goals>
            <goal>copy</goal>
         </goals>
      </execution>
   </executions>
</plugin>

 

 

You can also have it copy all the artifacts bound to a particular scope:

 

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-dependency-plugin</artifactId>
   <version>2.1</version>
   <executions>

      <execution>
         <id>copy-test-libs</id>
         <phase>process-test-resources</phase>
         <configuration>
            <includeScope>test</includeScope>
            <outputDirectory>
               ${project.build.directory}/test-libs
            </outputDirectory>
            <stripVersion>true</stripVersion>
         </configuration>
         <goals>
            <goal>copy-dependencies</goal>
         </goals>
      </execution>

   </executions>
</plugin>

 

Now you can reference the JARs you've prepared into the target/test-libs directory in your test case:

 

 

@Deployment
public static Archive<?> createTestArchive()
{
   WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war")
         .addClasses(...)
         .addAsLibraries(new File("target/test-libs/commons-lang.jar"))
         .addAsWebResource(EmptyAsset.INSTANCE, "beans.xml");
   // verify that the JAR files ended up in the WAR
   System.out.println(war.toString(true));
   return war;
}

 

 

Note: This approach assumes that the current working directory is the root of the project, which may not be the case in certain execution environments.

 

Looking ahead

 

We're also working on a native way to resolve dependencies in ShrinkWrap to bypass the whole build configuration. See ARQ-66. This planned functionality may be used either via the ShrinkWrap API or declared using annotations on the @Deployment method. We welcome your ideas.