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)
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.
Related
I have setup an embedded mongo via flapdoodle (de.flapdoodle.embed).
Quite a lot of mongo operations hence i would like to run all of them as a suite and setup the mongo just once in testsuite.
Now when i run the test cases via mvn install , it seems to run the test cases individually.
Is there a way to run test cases only from suite and not as a class.
baeldung.com describes the use of JUnit 5 Tags, which are very well suited for your case.
You can mark tests with two different tags:
#Test
#Tag("MyMongoTests")
public void testThatThisHappensWhenThatHappens() {
}
#Test
#Tag("MyTestsWithoutMongo")
public void testThatItDoesNotHappen() {
}
And execute either set in a suite, e.g.
#IncludeTags("MyMongoTests")
public class MyMongoTestSuite {
}
In your case, the tests could be categorized by whether Mongo is in the application context or not. So, theoretically, it might be possible to create a JUnit 5 Extension to add the tag. That would be the more complex solution though.
I am using mockito for my unit tests. Consider the following snippet
ThirdPartyClass tpo = mock(ThirdPartyClass.class);
doNothing().when(tpo).someMethod();
ThirdPartyClass is from a third party jar, lets say tp.jar. Now tp.jar is not an uber jar. Here is what ThirdPartyClass looks like
class ThirdPartyClass
{
SomeOtherClass memberObject;
public ThirdPartyClass(){}
/*Rest of the code*/
}
Now, when i try to run my unit tests, i get java.lang.ClassNotFoundException for SomeOtherClass. Remember that tp.jar is not an uber jar so it makes sense that SomeOtherClass is not in my classpath.
But why is mockito not able to handle this transitive dependency? Is there any way to ignore all transitive dependencies?
"Only mock types that you own" is the way to go here.
One reason for that is exactly what you describe: the test set up becomes too complicated, e.g. because of dependencies.
So instead of mocking the ThirdPartyClass, you create some sort of Adapter and then mock that class.
interface ThirdPartyAdapter {
void someMethod(); // only put method here that you really use
}
And then mock that thing instead:
ThirdPartyAdapter tpo = mock(ThirdPartyAdapter.class);
doNothing().when(tpo).someMethod();
And in production, delegate to the ThirdPartyClass:
class UsefulThing implements ThirdPartyAdapter {
ThirdPartyClass wrapped;
UsefulThing(ThirdPartyClass wrapped) {
this.wrapped = wrapped;
}
#Override
void someMethod() {
wrapped.someMethod()
}
}
Benefits:
Tests and production code is more robust to changes of the third party classes
You can use your own domain terms for methods instead of what the third party dictates
The relationship between your code and the third party code becomes clearer (and is only visible at a single place in your code)
I highly recommend Growing Object Oriented Software by Freeman & Pryce - or check the mockito docks for a quick start.
Mockito works by creating a subclass of the class to be mocked. So the class to be mocked has to compile. Could perhaps be solved by adding the jar that contains SomeOtherClass to your dependency management with scope test.
If you are going to mock ThirdPartyClass then SomeOtherClass MUST be on your compile-time and runtime classpath. Without seeing how you are building/running it's difficult to give real help
You need to ensure that all the required classes (jars) are on
The compile classpath
The runtime classpath
If you are using a build tool like Gradle or Maven to build & run then the classpaths, including transitive dependencies, will be managed for you. If you are compiling/running by hand you will need to ensure that the transitive dependencies are on the compile-time & runtime classpaths.
I have a maven mojo plugin with parameters like this:
public class SomeMojoPlugin
extends AbstractMojo
{
#Parameter( property = "templatefile", required = true )
private File templateFile;
And I want to write unit tests for this plugin.
How to pass this property/parameter "templatefile" in test methods?
The Maven documentation has a page about How To Use Maven Plugin Testing Harness.
Basically extending AbstractMojoTestCase, implementing the methods for the lifecycle and providing a sample pom.xml file for the test.
public class MyMojoTest extends AbstractMojoTestCase {
}
We have a multimodule Maven project and intend on performing tests on this. Because our tests are very homogenous, instead of writing the same test over and over, we wrote a parameterised test, which fetches all the files which stand to test and runs its tests against them. Now we want this as a maven plugin so you could just do something like mvn xquerytestrunner:test
I created a separate project, created a Java File in there and annotated it with
#Mojo( name = "xquerytester")
public class XQueryTestRunner extends AbstractMojo {
#Parameter( property = "xquerytester.querytotest", defaultValue = "" )
private static String queryToTest;
public void execute() throws MojoExecutionException, MojoFailureException {
JUnitCore junit = new JUnitCore();
Result result = junit.run(ParameterizedGenericXQueryTest.class);
}
}
Now my question. Will this run? And does it make sense?
My other option was to just have the test in the src/test/java folder of the main module and run it with mvn -Dtest=TestCircle test but the problem is that we use a plugin by Oracle ( oracle-soa-plugin ) that messes up everything around the project but we have to use it.
Our main pom.xml has <packaging>pom</packaging> which is why running the above test goal doesnt work. it just doesnt build or test anything. If i change it to jar the plugin throws errors during build. and I cannot skip build phase because the plugin just does its stuff anyways.
My goal is just to have a one liner for the console that runs my parameterized tests. It just seems Oracle didnt read the howtos on how to write a maven plugin and now i have to work with it.
Update 1:
We now went with a maven plugin. This can be run independent of the oracle soa plugin and also on a mvn project that has a "pom" packagint type.
The Mojo Class:
#Mojo( name = "xsl")
public class XslTestRunner {
#Parameter( property = "xsl.name", defaultValue = "" )
private static String name;
public void execute() throws MojoExecutionException, MojoFailureException {
JUnitCore junit = new JUnitCore();
Result result = junit.run(ParameterizedGenericXslTest.class);
PrintHelper.printResults(result, TransformationTestType.XQUERY);
}
}
The Maven Plugin Pom:
In general I followed these instructions: Your First Plugin
When writing code that interacts with external resources (such as using a web service or other network operation), I often structure the classes so that it can also be "stubbed" using a file or some other input method. So then I end up using the stubbed implementation to test other parts of the system and then one or two tests that specifically test calling the web service.
The problem is I don't want to be calling these external services either from Jenkins or when I run all of the tests for my project (e.g. "gradle test"). Some of the services have side effects, or may not be accessible to all developers.
Right now I just uncomment and then re-comment the #Test annotation on these particular test methods to enable and disable them. Enable it, run it manually to check it, then remember to comment it out again.
// Uncomment to test external service manually
//#Test
public void testSomethingExternal() {
Is there is a better way of doing this?
EDIT: For manual unit testing, I use Eclipse and am able to just right-click on the test method and do Run As -> JUnit test. But that doesn't work without the (uncommented) annotation.
I recommend using junit categories. See this blog for details : https://community.oracle.com/blogs/johnsmart/2010/04/25/grouping-tests-using-junit-categories-0.
Basically, you can annotate some tests as being in a special category and then you can set up a two test suites : one that runs the tests of that category and one that ignores tests in that category (but runs everything else)
#Category(IntegrationTests.class)
public class AccountIntegrationTest {
#Test
public void thisTestWillTakeSomeTime() {
...
}
#Test
public void thisTestWillTakeEvenLonger() {
....
}
}
you can even annotate individual tests"
public class AccountTest {
#Test
#Category(IntegrationTests.class)
public void thisTestWillTakeSomeTime() {
...
}
Anytime I see something manually getting turned on or off I cringe.
As far as I can see you use gradle and API for JUnit says that annotation #Ignore disables test. I will add gradle task which will add #Ignore for those tests.
If you're just wanting to disable tests for functionality that hasn't been written yet or otherwise manually disable some tests temporarily, you can use #Ignore; the tests will be skipped but still noted in the report.
If you are wanting something like Spring Profiles, where you can define rulesets for which tests get run when, you should either split up your tests into separate test cases or use a Filter.
You can use #Ignore annotation to prevent them from running automatically during test. If required, you may trigger such Ignored tests manually.
#Test
public void wantedTest() {
return checkMyFunction(10);
}
#Ignore
#Test
public void unwantedTest() {
return checkMyFunction(11);
}
In the above example, unwantedTest will be excluded.