Description
Hello,
I explored this new suggested solution for testing Maven plugins but then I realized it is unable to answer my (specific?) needs (from what I understood so far).
In fact, what I want is very simple: being able to dynamically generate Maven projects (in my case by providing an instance of MavenProject
built from a Model
). The main goal sought here is to follow DRY principle by avoiding duplicating pom.xml
files again and again with only a very small difference not always easy to spot most of the time. Well, with ITF and maven-invoker-plugin
, when the need is to only write few tests then that may be ok... But what about the case where you want to write dozens or maybe even hundreds of tests?
The maven-plugin-testing-harness
framework allows me to do that. ITF not (except if I'm wrong?).
Here is an example of (more or less generic) test that I have:
public final class MyMojoTest extends AbstractMojoTestCase {
private final FileSystem imfs = Jimfs.newFileSystem(Configuration.unix());
// Not used here, but used for making the assertions in fact
private final Path rootDir = imfs.getPath(".");
@Override
protected void tearDown() throws Exception {
super.tearDown();
imfs.close();
}
public void test_execute_nominalCase() {
// Assemble
TestConfig testConfig = NOMINAL_TEST_CONFIG;
MavenProject mavenProject = generateMavenProject(testConfig);
createInputFiles(testConfigs);
// Act
executeMyPlugin(mavenProject);
// Assert
assertGeneratedOutputFiles(testConfig);
}
// And then we can imagine lots of other tests here...
// ...
private void executeMyPlugin(@NotNull MavenProject mavenProject) {
try {
MyMojo mojo =
(MyMojo) lookupConfiguredMojo(mavenProject, "my-mojo");
mojo.injectCustomFileSystem(imfs);
mojo.execute();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Note: the logic behind the generateMavenProject()
method implementation may be not trivial at all depending on how complex the plugin's configuration model is. However, the base implementation of the method is the following one:
public final class MavenProjectGenerator {
public static @NotNull MavenProject generateMavenProject(
@Nullable Collection<TestConfig> configs) {
Model model = new Model();
model.setModelVersion("4.0.0");
model.setGroupId("my.group.id");
model.setArtifactId("a-test-project");
model.setVersion("0.0.0-TEST");
Build build = new Build();
build.addPlugin(generateMyPluginModel(configs));
model.setBuild(build);
var mavenProject = new MavenProject(model);
LOG.info("Generated POM content:\n{}", convertToXmlContent(mavenProject));
return mavenProject;
}
private static @NotNull Plugin generateMyPluginModel(
@Nullable Collection<TestConfig> configs) {
Plugin plugin = new Plugin();
plugin.setGroupId("my.group.id");
plugin.setArtifactId("my-test-plugin");
plugin.setConfiguration(generatePluginConfiguration(configs));
return plugin;
}
// And we build the configuration part of the plugin by relying on the Xpp3Dom class...
}
Based on this example, what I would expect from a modern Maven test framework is mainly just to have the possibility to rely on regular JUnit 5 tests with standard annotations like @Test
, @ParameterizedTest
and so on.
So this means: no abstract class like AbstractMojoTestCase
to extend, just a call like... Let's say randomly MojoTestExecutor#execute(MavenProject, String)
and that's all! I really think this is achievable.
So this would lead to the following result:
import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.Test;
// We no longer have to extend/implement any class/interface
final class MyMojoTest {
/*
* Since JUnit Jupiter v5.11 we can rely on @AutoClose annotation
* By default, the #close() method is called
* See: https://junit.org/junit5/docs/current/user-guide/#writing-tests-built-in-extensions-AutoClose
*/
@AutoClose private final MojoTestExecutor mojoTestExecutor = new MojoTestExecutor();
@AutoClose private final FileSystem imfs = Jimfs.newFileSystem(Configuration.unix());
// Not used here, but used for making the assertions in fact
private final Path rootDir = imfs.getPath(".");
// Nothing changed here
public void test_execute_nominalCase() {
// Assemble
TestConfig testConfig = NOMINAL_TEST_CONFIG;
MavenProject mavenProject = generateMavenProject(testConfig);
createInputFiles(testConfigs);
// Act
executeMyPlugin(mavenProject);
// Assert
assertGeneratedOutputFiles(testConfig);
}
// A lot more simpler!
private void executeMyPlugin(@NotNull MavenProject mavenProject) {
/*
* Here the proposed method signature is just a straight to the goal solution
* for sharing my idea without worrying about the details.
* The last parameter is supposed to permit injection of custom dependencies
* in the IoC container such as Guice, Spring, Quarkus, ...
* Here, I'm injecting an in-memory file system for improving tests performances,
* avoiding polluting the laptop FS in case of clean up failure
* and ensuring portability across platforms (Windows, Linux, MacOS, ...)
*/
mojoTestExecutor.execute(mavenProject, "my-mojo", imfs);
}
}
But maybe what should be done is simply make some evolutions on the maven-plugin-testing-harness
framework itself? But in this case then we should consider the fact ITF is only a better alternative to the maven-invoker-plugin
.
What do you think?