Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock maven plugin environment and or project configuration

I want to write unit tests (junit4) for my maven-plugin. All examples i found use "AbstractMojoTestCase" (junit3 :-(). To get rid of this i got answer here. But the problem is how Mojos get instantiated:

MyMojo myMojo = (MyMojo) lookupMojo( "touch", pom );

That means i need a pom for every test case - the pom is the tests input data. But is there a way to mock (i would use Mockito) the project model some how? Could lookupMojo(String groupId, String artifactId, String version, String goal, PlexusConfiguration pluginConfiguration) be a good starting point? In this case i would mock "PlexusConfiguration", but what methods? Some maven-plugin testing doku uses classes like "MavenProjectStub". But i can't get a consistent picture of how a mojo is created and to what intefaces it talks on creation.

A perfect solution would be if i could just

@inject
MyMojo testObject;

and just mock all the stuff it need to get it working (primary i need @Parameters)

like image 492
dermoritz Avatar asked Oct 30 '13 13:10

dermoritz


1 Answers

Based on my experience writing Maven plugin, there are two levels of testing a plugin: via unit test (using mocks) and via integration tests (using the maven-invoker-plugin).

For the integration tests, the maven archetype for new maven plugins already provide a good example out of the box, just execute the following and have a look at it:

 mvn archetype:generate \
  -DgroupId=sample.plugin \
  -DartifactId=hello-maven-plugin \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-plugin

By default you will get integration tests in a profile to start with. An example maven project will also be available (under src\it\simple-it\pom.xml) which can execute your plugin goals. What I suggest is also to enforce the result of your integration test via additional constraints in that pom.xml. For instance: you can add the Maven Enforcer Plugin rule to check against created files, if that makes sense for your plugin.

To answer more specifically to your question on how to write unit tests for custom maven plugins, this is the approach I'm using:

  • JUnit + Mockito.
  • Test case running using @RunWith(MockitoJUnitRunner.class)
  • Mock Maven specific classes (MavenProject, Log, Build, DependencyNode, etc.) using @Mock annotations
  • Initiate and link your mock objects in a @Before method (typically setUp() method)
  • Test your plugin :)

As an example, you might have the following mocked objects as class variable of your unit test:

@Mock
private MavenProject project;
@Mock
private Log log;
@Mock
Build build;

Then, in your @Before method you need to add a big of glue code as following:

Mockito.when(this.project.getBuild()).thenReturn(this.build);

For instance, I use to write some custom Enforcer Plugin rules, hence I need

@Mock
private EnforcerRuleHelper helper;

And in the @Before method:

    Mockito.when(this.helper.evaluate("${project}")).thenReturn(this.project);
    Mockito.when(this.helper.getLog()).thenReturn(this.log);
    Mockito.when(this.project.getBuild()).thenReturn(this.build);
    Mockito.when(this.helper.getComponent(DependencyGraphBuilder.class)).thenReturn(this.graphBuilder);
    Mockito.when(this.graphBuilder.buildDependencyGraph(this.project, null)).thenReturn(this.node);

As such, it will be easy to use these mock objects into your tests. For instance, a must have first dummy test is to test it against an empty build as following (below testing a custom Enforcer rule):

@Test
public void testEmptyBuild() throws Exception {
    try {
        this.rule.execute(this.helper);
    } catch (EnforcerRuleException e) {
        Assert.fail("Rule should not fail");
    }
}

If you need to test against dependencies of your build, for instance, you might end up writing utility methods as following:

private static DependencyNode generateNode(String groupId, String artifactId, String version) {
    DependencyNode node = Mockito.mock(DependencyNode.class);
    Artifact artifact = Mockito.mock(Artifact.class);
    Mockito.when(node.getArtifact()).thenReturn(artifact);
    // mock artifact
    Mockito.when(artifact.getGroupId()).thenReturn(groupId);
    Mockito.when(artifact.getArtifactId()).thenReturn(artifactId);
    Mockito.when(artifact.getVersion()).thenReturn(version);

    return node;
}

In order to easily create dependencies into the dependency graph of your build, as following:

List<DependencyNode> nodes = new ArrayList<DependencyNode>();
nodes.add(generateNode("junit", "junit", "4.12"));

Mockito.when(node.getChildren()).thenReturn(nodes);

NOTE: you can improve the utility method if you need further details (like scope or classifier for a dependency).

If you also need to mock configuration of a plugin, because you need to scan existing plugins and their configuration, for instance, you can do it as following:

List<Plugin> plugins = new ArrayList<Plugin>();
Plugin p = new Plugin(); // no need to mock it
p.setArtifactId("maven-surefire-plugin");
Xpp3Dom conf = new Xpp3Dom("configuration");
Xpp3Dom skip = new Xpp3Dom("skip");
skip.setValue("true");
conf.addChild(skip);
p.setConfiguration(conf);
plugins.add(p);

Mockito.when(this.build.getPlugins()).thenReturn(plugins);

I will obviously not cover all the possible cases, but I am sure you got an understanding about approach and usage. Hope it helps.

like image 177
A_Di-Matteo Avatar answered Sep 30 '22 11:09

A_Di-Matteo