I am currently attempting to write a Maven plugin using GMavenPlus (thank you @Keegan!) and Groovy 2.4.3. In a nutshell, the plugin parses a directory of SQL DDLs and generates output from those parsed DDLs
The Mojo itself works just fine when built, imported, and run within a full consuming project. Horrah!
The problem is with unit testing. When trying to unit test this Mojo, Maven POM vars like ${project.basedir}
are not being
expanded and thus the mojo is failing with an error like "File not found! [${project.basedir}/src/test/resources/ddl]". As
you can see from that error message, ${project.basedir}
was passed as a literal instead of being expanded.
I am currently using the Maven Plugin Testing Harness (with the fixed dependencies, see this blog), JUnit 4.12, and AssertJ 3.0.0 as my testing stack.
Any ideas or specific tricks to get things like project.basedir to expand in a unit test?
Thanks in advance!
Unit Test in Question:
import edge.SqlToScalaMojo
import org.junit.Before
import org.junit.Test
/**
* Created by medge on 6/15/15.
*/
class SqlToScalaMojoTest extends BaseMojoTest<SqlToScalaMojo> {
SqlToScalaMojo mojo
@Before
void setup() {
mojo = getMojo("parse-ddls")
}
@Test
void testMojoExecution() throws Exception {
assertThat mojo isNotNull()
mojo.execute()
}
}
BaseMojoTest.groovy (really just a convenience base class):
import org.apache.maven.plugin.AbstractMojo
import org.apache.maven.plugin.testing.MojoRule
import org.junit.Rule
/**
* Base Test class for Mojo tests. Extends {@link org.assertj.core.api.Assertions}
*
* If a type is given to this class then the result of #getMojo() does not have to be cast, reducing the amount of code
* to be written in the unit tests themselves.
*
* Created by medge on 6/5/15.
*/
abstract class BaseMojoTest<T extends AbstractMojo> extends org.assertj.core.api.Assertions {
/**
* MojoRule used to lookup Mojos
*/
@Rule public MojoRule rule = new MojoRule()
/**
* Get a configured mojo using the default pom file. Calls #getMojo(goal, getPom()) implicitly
*
* @param goal Goal to look up
* @return T configured Mojo
*/
T getMojo(String goal) {
getMojo(goal, getPom())
}
/**
* Get a configured mojo using the specified pom file
*
* @param goal Goal to look up
* @param pom POM file to use when configuring Mojo
* @return T configured Mojo
*/
T getMojo(String goal, File pom) {
T mojo = (T) rule.lookupMojo(goal, pom)
mojo
}
/**
* Default POM file if no custom path is given
*/
String defaultPomPath = "src/test/resources/plugin-config.xml"
/**
* Return a File reference containing the default POM file
*
* @return File
*/
File getPom() {
getPom(defaultPomPath)
}
/**
* Return a File reference containing the POM file found at the specified path. Implicitly asserts that the POM
* exists using <code>assertFile</code>
*
* @param path Path to user-defined POM (overrides the default if provided)
* @return File containing the specified POM.
*/
File getPom(String path) {
File _pom = getTestFile(path)
// Implicitly assert POM exists
assertFile(_pom)
// Then return the POM file
_pom
}
/**
* Convenience method to assert that a file is valid
*
* @param file File to validate
*/
static void assertFile(File file) {
assertThat file isNotNull()
assertThat file exists()
}
/**
* Get the current project's base directory. From {@link org.codehaus.plexus.PlexusTestCase}
*
* @return Base directory path
*/
static String getBaseDir() {
final String path = System.getProperty( "basedir" );
path ?: new File( "" ).getAbsolutePath();
}
/**
* Return a test file from the src/test/resources directory. Assumes the base directory is src/test/resources so the
* src/test/resources prefix can be omitted from the path if desired
*
* @param path File path
* @return File
*/
static File getTestFile(String path) {
File testFile
if(path.indexOf("src/test/resources/") > -1)
testFile = getTestFile(getBaseDir(), path)
else
testFile = getTestFile(getBaseDir(), "src/test/resources/${path}")
testFile
}
/**
* Retrieve a test file from the given baseDir/path
*
* @param baseDir String base directory to look in
* @param path String path to the file desired
* @return File
*/
static File getTestFile(String baseDir, String path) {
new File(baseDir, path)
}
}
Main POM file for the Mojo itself:
<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edge</groupId>
<artifactId>parser-mojo</artifactId>
<version>0.0.3-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<properties>
<groovy.version>2.4.3</groovy.version>
<maven.version>3.3.3</maven.version>
<junit.version>4.12</junit.version>
<assertj.version>3.0.0</assertj.version>
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<!-- Dependencies for Maven Mojos -->
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.22</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>${maven.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-testing</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
<version>3.3.0</version>
<scope>test</scope>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>testGenerateStubs</goal>
<goal>testCompile</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.4</version>
<configuration>
<!-- see http://jira.codehaus.org/browse/MNG-5346 -->
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
<executions>
<execution>
<id>generate-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
<execution>
<id>help-goal</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Test POM used during the unit test:
<project>
<build>
<plugins>
<plugin>
<groupId>edge</groupId>
<artifactId>parser-mojo</artifactId>
<version>0.0.3-SNAPSHOT</version>
<configuration>
<template>${project.basedir}/src/test/resources/sample.template</template>
<inputDir>${project.basedir}/src/test/resources/ddl</inputDir>
<outputDir>${project.basedir}/src/test/resources/generated/</outputDir>
</configuration>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>parse-ddls</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Thanks!
Found the answer to my own question. Posting here in case anybody else is looking for the same issue.
First, I was running into another issue with the Mojo itself in regards to the
@Parameter
annotation. The following:Will generate an error because the Groovy compiler will pick up the string
"${project.basedir}/src/main/resources"
before Maven. It will then do its evaluation and convert it to a GString. This generates an error because the annotation expects a java.lang.String but it will get java.lang.ObjectThe solution, as pointed out by @Keegan in another question, is to use single quotes instead of double quotes:
The Groovy compiler won't evaluate the string and Maven will pick up from there
Next, the original question about defaultValue's not being read during the unit test. The culprit was this method in BaseMojoTest.groovy:
Specifically, this part:
lookupMojo on a MojoRule does not evaluate Parameter annotation's default value. It expects all possible parameters are present in the test POM file. A quick dive into the source for MojoRule and I found a way to fix it:
The
getMavenProject()
method is a slight variation to the code found inMojoRule#readMavenProject()
, altered to reference the MojoRule and my definition of getBaseDir(). The Mojos generated by this method now properly evaluate @Parameter's defaultValue! I do feel likegetMavenProject()
could probably be made more efficient (instead of creating a MavenProject every time) but this will suffice for now.One more thing
You may have noticed this line from the
getMavenProject()
method:This very annoying little hack is necessary because when creating the MavenProject object:
${project.basedir} actually becomes the basedir of the test POM file used in the unit test. If, like me, you have a test POM in src/test/resources then basedir will become something like /Users/medge/.../src/test/resources. Ergo, when @Parameter annotations with ${project.basedir} are expanded, such as:
When ran from a unit test, inputDir will resolve to
/Users/medge/.../src/test/resources/src/main/resources
Subtle things can trip you up...
Hope this is helpful to someone else!