DEV Community

Karl Heinz Marbaise
Karl Heinz Marbaise

Posted on

Maven Plugin Testing - In a Modern way - Part IV

In the previous part of the series - Maven Plugin Testing - In a Modern way - Part III we have seen how to define command line options. In this part we will take a deeper look which goals will run for each test case and how we can change that.

Let us start with simple example test case like the following:

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  void the_first_test_case(MavenExecutionResult result) {
     ...
  }
}
Enter fullscreen mode Exit fullscreen mode

If we ran that integration test Maven will be called like the following:

mvn -Dmaven.repo.local=<Directory> --batch-mode --show-version --errors package
Enter fullscreen mode Exit fullscreen mode

We will concentrate on the part package in the above example. This is a life cycle phase of Maven. So what can we do if we like to call something like mvn .. verify instead? This can simply being achieved by using the @MavenGoal annotation like this:

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  @MavenGoal("verify")
  void the_first_test_case(MavenExecutionResult result) {
     ...
  }
}
Enter fullscreen mode Exit fullscreen mode

So let us take a deeper look onto the following example:

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  @MavenGoal("verify")
  void first(MavenExecutionResult result) {
     ...
  }

  @MavenTest
  void second(MavenExecutionResult result) {
     ...
  }

  @MavenTest
  void third(MavenExecutionResult result) {
     ...
  }
}
Enter fullscreen mode Exit fullscreen mode

The test case first will be called with the phase verify whereas second and third will be called with the package. So this means you can overwrite the default behaviour for each test case separately.

Sometimes you want to execute Maven during an integration test like this: mvn clean verify or in general with multiple life cycle phase. This can be achieved by using multiple MavenGoal annotations as in the following example:

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  @MavenGoal("clean")
  @MavenGoal("verify")
  void first(MavenExecutionResult result) {
     ...
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Of course there are situations where you have a bunch of integration tests which needed to be executing the previously defined goals. This can handled by defining the @MavenGoal annotation on a class level instead like this:

@MavenJupiterExtension
@MavenGoal("clean")
@MavenGoal("verify")
class BaseIT {

  @MavenTest
  void first(MavenExecutionResult result) {
     ...
  }

  @MavenTest
  void second(MavenExecutionResult result) {
     ...
  }

  @MavenTest
  void third(MavenExecutionResult result) {
     ...
  }
}
Enter fullscreen mode Exit fullscreen mode

This also gives the opportunity to let run a single test (or more than one) case within a test class with different goals depending on what you like to achieve.

Another example on how to define the @MavenGoal annotation on a class level which looks like this:

@MavenJupiterExtension
@MavenGoal("clean")
class GoalsOnClassIT {

  @MavenTest
  @DisplayName("This will check the goal which is defined on the class.")
  void goal_clean(MavenExecutionResult result) {
    assertThat(result)
        .isSuccessful()
        .out()
        .info()
        .containsSubsequence(
            "Scanning for projects...",
            "-------------------< com.soebes.katas:kata-fraction >-------------------",
            "Building kata-fraction 1.0-SNAPSHOT",
            "--------------------------------[ jar ]---------------------------------",
            "--- maven-clean-plugin:3.1.0:clean (default-clean) @ kata-fraction ---"
        );
    assertThat(result)
        .isSuccessful()
        .out()
        .warn().isEmpty();
  }
}
Enter fullscreen mode Exit fullscreen mode

The next logical step is to create a meta annotation to make life easier. We would like to combine clean and verify within a single annotation @GoalsCleanVerify which can be done like this:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RUNTIME)
@Inherited
@MavenGoal({"clean", "verify"})
public @interface GoalsCleanVerify {
}
Enter fullscreen mode Exit fullscreen mode

Such kind of meta annotation can be used on class level (defined by the annotation itself) as well as on method level like this:

@MavenJupiterExtension
@GoalsCleanVerify
class MetaAnnotationGoalIT {
 ...
}
Enter fullscreen mode Exit fullscreen mode

So now lets think about Part III where we defined this meta annotation:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RUNTIME)
@Inherited
@MavenOption(MavenCLIOptions.FAIL_AT_END)
@MavenOption(MavenCLIOptions.NON_RECURSIVE)
@MavenOption(MavenCLIOptions.ERRORS)
@MavenOption(MavenCLIOptions.DEBUG)
public @interface MavenTestOptions {
}
Enter fullscreen mode Exit fullscreen mode

This meta annotation can now being enhanced by the needed @MavenGoal annotations.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RUNTIME)
@Inherited
@MavenOption(MavenCLIOptions.FAIL_AT_END)
@MavenOption(MavenCLIOptions.NON_RECURSIVE)
@MavenOption(MavenCLIOptions.ERRORS)
@MavenOption(MavenCLIOptions.DEBUG)
@MavenGoal("clean")
@MavenGoal("verify")
public @interface MavenTestOptions {
}
Enter fullscreen mode Exit fullscreen mode

This means you can define easily your set of annotation or combination of command line options and goals or more sophisticated combine it with @MavenJupiterExtension
like this:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RUNTIME)
@Inherited
@MavenJupiterExtension
@MavenOption(MavenCLIOptions.FAIL_AT_END)
@MavenOption(MavenCLIOptions.NON_RECURSIVE)
@MavenOption(MavenCLIOptions.ERRORS)
@MavenOption(MavenCLIOptions.DEBUG)
@MavenGoal("clean")
@MavenGoal("verify")
public @interface MavenTestOptions {
}
Enter fullscreen mode Exit fullscreen mode

This will give us the option to use it like this:

@MavenTestOptions
class FailureIT {

  @MavenTest
  void case_one(MavenExecutionResult project) {
    ..
  }

  @MavenTest
  void case_two(MavenExecutionResult result) {
    ..
  }

  @MavenTest
  void case_three(MavenExecutionResult result) {
    ..
  }

  @MavenTest
  @MavenOption(MavenCLIOptions.DEBUG)
  void case_four(MavenExecutionResult result) {
    ..
  }

}
Enter fullscreen mode Exit fullscreen mode

This combines the given options with the defined goals in one single annotation. If you need to change something you have to fix only a single point.

So did we miss something? Yes we did. Sometimes you want to call your plugin with a separate goal like this:

mvn org.test.maven.plugin:maven-x-plugin:goal
Enter fullscreen mode Exit fullscreen mode

This can be achieved by using the @MavenGoal annotation like this:

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  @MavenGoal("org.test.maven.plugin:maven-x-plugin:goal")
  void first(MavenExecutionResult result) {
     ...
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Now let us assume the given plugin is the one which should be tested via the given integration test. Then you need to define the correct groupId, artifactId, version and the
correct goal. Unfortunately with each release of your plugin the version changes etc.

@MavenJupiterExtension
class BaseIT {

  @MavenTest
  @MavenGoal("${project.groupId}:${project.artifactId}:${project.version}:goal-to-test")
  void first(MavenExecutionResult result) {
     ...
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

The placeholders ${project.groupId}, ${project.artifactId} and ${project.version} are
exactly the information from your project pom in which you define the coordinates of the plugin your are developing. This makes sure only this plugin version is being used
and not any other version is being tried to download from central or other repositories during your integration tests.

Good examples can be found in
maven-invoker-plugin based integration tests.

So this it is for Part IV. If you like to learn more about the Integration Testing Framework you can consult the users guide. If you like to know the state of the release you can take a look into the release notes.

If you have ideas, suggestions or found bugs please file in an issue on github.

An example project which shows the previous example can be found on GitHub.

Top comments (0)