Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion maven-plugin-testing-harness/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ under the License.
<description>The Maven Plugin Testing Harness provides mechanisms to manage tests on Mojo.</description>

<properties>
<versions.junit5>5.13.0</versions.junit5>
<mavenVersion>3.9.12</mavenVersion>
<resloverVersion>1.9.25</resloverVersion>
<wagonVersion>3.5.3</wagonVersion>
</properties>

Expand Down Expand Up @@ -61,6 +62,12 @@ under the License.
<version>${mavenVersion}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${version.maven-plugin-tools}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
Expand Down Expand Up @@ -110,6 +117,27 @@ under the License.
<optional>true</optional>
</dependency>

<!-- START SNIPPET: resolver-transport -->
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>${resloverVersion}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-file</artifactId>
<version>${resloverVersion}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-http</artifactId>
<version>${resloverVersion}</version>
<scope>test</scope>
</dependency>
<!-- END SNIPPET: resolver-transport -->

<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-testing</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -50,9 +51,14 @@
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.internal.ProviderMethodsModule;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.api.di.Provides;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecution;
Expand Down Expand Up @@ -81,6 +87,7 @@
import org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.eclipse.aether.RepositorySystemSession;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
Expand All @@ -93,6 +100,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.spy;

/**
* JUnit Jupiter extension that provides support for testing Maven plugins (Mojos).
Expand Down Expand Up @@ -223,6 +231,9 @@ public void beforeEach(ExtensionContext context) throws Exception {
MojoExecution mojoExecution = addMock(plexusContainer, MojoExecution.class, this::mockMojoExecution);
MavenSession mavenSession = addMock(plexusContainer, MavenSession.class, this::mockMavenSession);

// prepare MavenExecutionRequest to be available in BeforeEach methods in test classes
createMavenExecutionRequest(context);

SessionScope sessionScope = plexusContainer.lookup(SessionScope.class);
sessionScope.enter();
sessionScope.seed(MavenSession.class, mavenSession);
Expand Down Expand Up @@ -388,6 +399,11 @@ protected Mojo lookupMojo(
ExtensionContext extensionContext, String[] coord, Xpp3Dom pluginConfiguration, PluginDescriptor descriptor)
throws Exception {
PlexusContainer plexusContainer = getContainer(extensionContext);

MavenExecutionRequest request = setupMavenExecutionRequest(extensionContext);
plexusContainer.lookup(MavenExecutionRequestPopulator.class).populateDefaults(request);
setupRepositorySession(extensionContext, request);

// pluginkey = groupId : artifactId : version : goal
Mojo mojo = plexusContainer.lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]);

Expand All @@ -405,15 +421,18 @@ protected Mojo lookupMojo(
MojoExecution mojoExecution = plexusContainer.lookup(MojoExecution.class);

if (mockingDetails(session).isMock()) {
lenient().when(session.getCurrentProject()).thenReturn(mavenProject);
lenient().doReturn(mavenProject).when(session).getCurrentProject();
}

if (mockingDetails(mavenProject).isMock()) {
lenient().when(mavenProject.getBasedir()).thenReturn(new File(getTestBasedir(extensionContext)));
lenient()
.doReturn(new File(getTestBasedir(extensionContext)))
.when(mavenProject)
.getBasedir();
}

if (mojoDescriptor.isPresent() && mockingDetails(mojoExecution).isMock()) {
lenient().when(mojoExecution.getMojoDescriptor()).thenReturn(mojoDescriptor.get());
lenient().doReturn(mojoDescriptor.get()).when(mojoExecution).getMojoDescriptor();
}

if (pluginConfiguration != null) {
Expand Down Expand Up @@ -445,6 +464,100 @@ protected Mojo lookupMojo(
return mojo;
}

private boolean isRealRepositorySessionNotRequired(ExtensionContext context) {
return !AnnotationSupport.findAnnotation(context.getTestClass(), MojoTest.class)
.map(MojoTest::realRepositorySession)
.orElse(false);
}

/**
* Create a MavenExecutionRequest if not already present in the MavenSession
*/
private void createMavenExecutionRequest(ExtensionContext context) throws ComponentLookupException {
PlexusContainer container = getContainer(context);
MavenSession session = container.lookup(MavenSession.class);
MavenExecutionRequest request = session.getRequest();

if (request == null && mockingDetails(session).isMock()) {
lenient()
.doReturn(spy(new DefaultMavenExecutionRequest()))
.when(session)
.getRequest();
}
}

private MavenExecutionRequest setupMavenExecutionRequest(ExtensionContext context) throws ComponentLookupException {
PlexusContainer container = getContainer(context);
MavenSession session = container.lookup(MavenSession.class);
MavenExecutionRequest request = session.getRequest();

if (request == null) {
// user can provide own MavenSession instance without a request
request = new DefaultMavenExecutionRequest();
}

if (request.getStartTime() == null) {
request.setStartTime(new Date());
}

if (request.getUserProperties().isEmpty()) {
request.setUserProperties(session.getUserProperties());
}

if (request.getSystemProperties().isEmpty()) {
request.setSystemProperties(session.getSystemProperties());
}

// set a default local repository path if none is set
if (request.getLocalRepositoryPath() == null && request.getLocalRepository() == null) {
request.setLocalRepositoryPath(getTestBasedir(context) + "/target/local-repo");
}

if (request.getBaseDirectory() == null) {
request.setBaseDirectory(new File(getTestBasedir(context)));
}

return request;
}

private void setupRepositorySession(ExtensionContext context, MavenExecutionRequest request)
throws ComponentLookupException {

if (isRealRepositorySessionNotRequired(context)) {
return;
}

PlexusContainer container = getContainer(context);

MavenProject mavenProject = container.lookup(MavenProject.class);
if (mockingDetails(mavenProject).isMock()) {
lenient()
.doReturn(request.getRemoteRepositories())
.when(mavenProject)
.getRemoteArtifactRepositories();
lenient()
.doReturn(request.getPluginArtifactRepositories())
.when(mavenProject)
.getPluginArtifactRepositories();
lenient()
.doReturn(RepositoryUtils.toRepos(request.getRemoteRepositories()))
.when(mavenProject)
.getRemoteProjectRepositories();
lenient()
.doReturn(RepositoryUtils.toRepos(request.getPluginArtifactRepositories()))
.when(mavenProject)
.getRemotePluginRepositories();
}

RepositorySystemSession repositorySystemSession =
container.lookup(DefaultRepositorySystemSessionFactory.class).newRepositorySession(request);

MavenSession session = container.lookup(MavenSession.class);
if (mockingDetails(session).isMock()) {
lenient().doReturn(repositorySystemSession).when(session).getRepositorySession();
}
}

private Xpp3Dom finalizeConfig(Xpp3Dom config, MojoDescriptor mojoDescriptor) {
List<Xpp3Dom> children = new ArrayList<>();
if (mojoDescriptor != null && mojoDescriptor.getParameters() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,12 @@
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(MojoExtension.class)
@Target(ElementType.TYPE)
public @interface MojoTest {}
public @interface MojoTest {
/**
* Indicates whether to use a real repository session for the test.
* <br>
* When set to {@code true}, the test will utilize a real repository session,
* allowing for artifact resolution and repository interactions.
*/
boolean realRepositorySession() default false;
}
123 changes: 16 additions & 107 deletions maven-plugin-testing-harness/src/site/markdown/examples/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,129 +18,38 @@ date: February 2008
<!-- KIND, either express or implied. See the License for the -->
<!-- specific language governing permissions and limitations -->
<!-- under the License. -->
## Testing Using Repositories

### NOTE

`JUnit 3` based tests are deprecated since `3.4.0`.

Use JUnit 5 annotations, consult [javadocs](../apidocs/org/apache/maven/api/plugin/testing/package-summary.html) for examples.

**Note**: This example improves the [cookbook](../getting-started/index.html) for testing repositories.


When developing a Maven plugin you often need to play with repositories. Suppose that the MyMojo needs to download artifacts into your local repository, i.e.:



```
public class MyMojo
extends AbstractMojo
{
/**
* Used for resolving artifacts
*/
@Component
private ArtifactResolver resolver;

/**
* Factory for creating artifact objects
*/
@Component
private ArtifactFactory factory;

/**
* Local Repository.
*/
@Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
private ArtifactRepository localRepository;

public void execute()
throws MojoExecutionException
{
...

Artifact artifact = factory.createArtifact( "junit", "junit", "3.8.1", "compile", "jar" );
try
{
resolver.resolve( artifact, project.getRemoteArtifactRepositories(), localRepository );
}
catch ( ArtifactResolutionException e )
{
throw new MojoExecutionException( "Unable to resolve artifact:" + artifact, e );
}
catch ( ArtifactNotFoundException e )
{
throw new MojoExecutionException( "Unable to find artifact:" + artifact, e );
}

...
}
}
```

### Create Stubs
## Testing Using Repositories

**Note**: This example improves the [cookbook](../getting-started/index.html) for testing repositories.

When developing a Maven plugin you often need to play with repositories.
Suppose that the Mojo needs to download artifacts into your local repository.

Stub for the test project:
You need annotate unit test with `@MojoTest(realRepositorySession = true)` to enable real repository session.

Then provided mock for `MavenSession` will have a real repository session with local repository configured.

Mock for `MavenProject` will also have mocked methods `getRemote*Repositories`.

```
public class MyProjectStub
extends MavenProjectStub
{
/**
* Default constructor
*/
public MyProjectStub()
{
...
}
### Project dependencies for test

/** {@inheritDoc} */
public List getRemoteArtifactRepositories()
{
ArtifactRepository repository = new DefaultArtifactRepository( "central", "http://repo.maven.apache.org/maven2",
new DefaultRepositoryLayout() );
For real repository session you need to add resolver transport dependency in test scope to your `pom.xml`:

return Collections.singletonList( repository );
}
}
```
<!-- MACRO{snippet|id=resolver-transport|file=maven-plugin-testing-harness/pom.xml} -->

### Example Mojo to test

### Configure `project-to-test` pom
<!-- MACRO{snippet|id=resolve-mojo|file=maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojo.java} -->

### Unit test

<!-- MACRO{snippet|id=resolve-mojo-test|file=maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojoTest.java} -->

```
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-my-plugin</artifactId>
<configuration>
<!-- Specify where this pom will output files -->
<outputDirectory>${basedir}/target/test-harness/project-to-test</outputDirectory>

<!-- By default <<<${basedir}/target/local-repo", where basedir refers
to the basedir of maven-my-plugin. -->
<localRepository>${localRepository}</localRepository>
<!-- The defined stub -->
<project implementation="org.apache.maven.plugin.my.stubs.MyProjectStub"/>
</configuration>
</plugin>
</plugins>
</build>
</project>
```

#### Execute test


Calling `mvn test` will create `$\{basedir\}/target/local-repo/junitjunit/3.8.1/junit-3.8.1.jar` file.
Calling `mvn test` will create `<temp-directory>/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0.jar` file.



Expand Down
Loading