Maven Plugin Testing - In a Modern way - Part IV
In the prevous 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:
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 void the_first_test_case(MavenExecutionResult result) {
6 ...
7 }
8}
If we ran that integration test Maven will be called like the following:
1mvn -Dmaven.repo.local=<Directory> --batch-mode --show-version --errors package
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:
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 @MavenGoal("verify")
6 void the_first_test_case(MavenExecutionResult result) {
7 ...
8 }
9}
So let us take a deeper look onto the following example:
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 @MavenGoal("verify")
6 void first(MavenExecutionResult result) {
7 ...
8 }
9
10 @MavenTest
11 void second(MavenExecutionResult result) {
12 ...
13 }
14
15 @MavenTest
16 void third(MavenExecutionResult result) {
17 ...
18 }
19}
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:
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 @MavenGoal("clean")
6 @MavenGoal("verify")
7 void first(MavenExecutionResult result) {
8 ...
9 }
10 ...
11}
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:
1@MavenJupiterExtension
2@MavenGoal("clean")
3@MavenGoal("verify")
4class BaseIT {
5
6 @MavenTest
7 void first(MavenExecutionResult result) {
8 ...
9 }
10
11 @MavenTest
12 void second(MavenExecutionResult result) {
13 ...
14 }
15
16 @MavenTest
17 void third(MavenExecutionResult result) {
18 ...
19 }
20}
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:
1@MavenJupiterExtension
2@MavenGoal("clean")
3class GoalsOnClassIT {
4
5 @MavenTest
6 @DisplayName("This will check the goal which is defined on the class.")
7 void goal_clean(MavenExecutionResult result) {
8 assertThat(result)
9 .isSuccessful()
10 .out()
11 .info()
12 .containsSubsequence(
13 "Scanning for projects...",
14 "-------------------< com.soebes.katas:kata-fraction >-------------------",
15 "Building kata-fraction 1.0-SNAPSHOT",
16 "--------------------------------[ jar ]---------------------------------",
17 "--- maven-clean-plugin:3.1.0:clean (default-clean) @ kata-fraction ---"
18 );
19 assertThat(result)
20 .isSuccessful()
21 .out()
22 .warn().isEmpty();
23 }
24}
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:
1@Target({ElementType.METHOD, ElementType.TYPE})
2@Retention(RUNTIME)
3@Inherited
4@MavenGoal({"clean", "verify"})
5public @interface GoalsCleanVerify {
6}
Such kind of meta annotation can be used on class level (defined by the annotation itself) as well as on method level like this:
1@MavenJupiterExtension
2@GoalsCleanVerify
3class MetaAnnotationGoalIT {
4 ...
5}
So now lets think about Part III where we defined this meta annotation:
1@Target({ElementType.METHOD, ElementType.TYPE})
2@Retention(RUNTIME)
3@Inherited
4@MavenOption(MavenCLIOptions.FAIL_AT_END)
5@MavenOption(MavenCLIOptions.NON_RECURSIVE)
6@MavenOption(MavenCLIOptions.ERRORS)
7@MavenOption(MavenCLIOptions.DEBUG)
8public @interface MavenTestOptions {
9}
This meta annotation can now being enhanced by the needed @MavenGoal
annotations.
1@Target({ElementType.METHOD, ElementType.TYPE})
2@Retention(RUNTIME)
3@Inherited
4@MavenOption(MavenCLIOptions.FAIL_AT_END)
5@MavenOption(MavenCLIOptions.NON_RECURSIVE)
6@MavenOption(MavenCLIOptions.ERRORS)
7@MavenOption(MavenCLIOptions.DEBUG)
8@MavenGoal("clean")
9@MavenGoal("verify")
10public @interface MavenTestOptions {
11}
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:
1@Target({ElementType.METHOD, ElementType.TYPE})
2@Retention(RUNTIME)
3@Inherited
4@MavenJupiterExtension
5@MavenOption(MavenCLIOptions.FAIL_AT_END)
6@MavenOption(MavenCLIOptions.NON_RECURSIVE)
7@MavenOption(MavenCLIOptions.ERRORS)
8@MavenOption(MavenCLIOptions.DEBUG)
9@MavenGoal("clean")
10@MavenGoal("verify")
11public @interface MavenTestOptions {
12}
This will give us the option to use it like this:
1@MavenTestOptions
2class FailureIT {
3
4 @MavenTest
5 void case_one(MavenExecutionResult project) {
6 ..
7 }
8
9 @MavenTest
10 void case_two(MavenExecutionResult result) {
11 ..
12 }
13
14 @MavenTest
15 void case_three(MavenExecutionResult result) {
16 ..
17 }
18
19 @MavenTest
20 @MavenOption(MavenCLIOptions.DEBUG)
21 void case_four(MavenExecutionResult result) {
22 ..
23 }
24
25}
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:
1mvn org.test.maven.plugin:maven-x-plugin:goal
This can be achieved by using the @MavenGoal
annotation like this:
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 @MavenGoal("org.test.maven.plugin:maven-x-plugin:goal")
6 void first(MavenExecutionResult result) {
7 ...
8 }
9 ...
10}
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.
1@MavenJupiterExtension
2class BaseIT {
3
4 @MavenTest
5 @MavenGoal("${project.groupId}:${project.artifactId}:${project.version}:goal-to-test")
6 void first(MavenExecutionResult result) {
7 ...
8 }
9 ...
10}
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.