How do you write integration tests for a Maven plugin, and what role does the maven-invoker-plugin play?
answer
- src/it/<name>/pom.xml sample projects
- install goal -> staging local repo
- invoker.properties = goals/result
- verify.groovy post-build asserts
- gate behind run-its profile
basics
~20 sUnit tests check the Mojo class in isolation, but integration tests run the plugin inside a real Maven build. The maven-invoker-plugin executes small sample projects under src/it/, runs goals against your freshly-installed plugin, and verifies the results.
solid answer
~50 sPlugin integration tests run sample projects through a *real* Maven invocation, which is the only way to verify descriptor generation, parameter injection, and lifecycle binding end-to-end. The **maven-invoker-plugin** drives this: each test is a little project under `src/it/<name>/` with its own `pom.xml`. Its `install` goal first installs the plugin (and dependencies) into a local staging repo so the ITs resolve the version under test; then `integration-test`/`run` invokes each sample project's build. You assert outcomes with: `invoker.properties` (which goals to run, expected exit, profiles), a `verify.groovy`/`verify.bsh` post-build script for assertions, and `expected-results` / build-log greps. It's typically bound to the `integration-test` phase and gated behind a `run-its` profile so it doesn't slow normal builds. For pure-Java unit testing of a Mojo you'd instead use `maven-plugin-testing-harness` (`AbstractMojoTestCase`) or just construct the Mojo and set fields, but that can't validate the generated descriptor or real injection — hence invoker ITs complement it.
code
bash · 7 lines# Run the plugin's integration tests (sample projects under src/it/)
mvn clean install -Prun-its
# maven-invoker-plugin will:
# 1. install the freshly built plugin into target/local-repo
# 2. run each src/it/*/pom.xml build into target/it/*
# 3. execute verify.groovy assertions and fail on any IT failurego deeper
Knows plugins need integration tests that run real builds, not just unit tests.
Lays out src/it projects with invoker.properties and a verify script and wires the invoker plugin.
Designs the staging-repo isolation, run-its profiling, and chooses harness vs. invoker per concern.
Establishes plugin test strategy/CI matrix (multiple Maven/JDK versions) and reproducibility standards across the org's plugins.
## Two layers of testing 1. **Unit tests** — instantiate the Mojo, set its fields, call `execute()`, assert. The **maven-plugin-testing-harness** (`AbstractMojoTestCase`) can build a Mojo from a test POM and inject parameters. Fast, but it does *not* exercise the generated `plugin.xml`, real dependency resolution, or lifecycle binding. 2. **Integration tests** — run a complete Maven build that uses your plugin, end to end. This is what the **maven-invoker-plugin** is for. ## How maven-invoker-plugin works It runs a set of **sample projects** (one folder each) through real `mvn` invocations and checks the results. **Layout:** ``` src/it/ settings.xml # points at the staging local repo simple-it/ pom.xml # uses ${project.version} of the plugin under test invoker.properties # goals, expected result, profiles verify.groovy # post-build assertions ``` **Typical goals/bindings:** - `install` — copies the just-built plugin + its dependencies into an isolated local repository (`target/local-repo`) so the IT projects resolve the *version under test*, not a stale released one. - `integration-test` (alias `run`) — clones each `src/it/*` project to `target/it/*` and runs its build. - `verify` — fails the outer build if any IT failed. **Per-test control — `invoker.properties`:** ```properties invoker.goals = clean verify invoker.buildResult = success invoker.profiles = ci ``` **Post-build assertions — `verify.groovy`:** ```groovy assert new File(basedir, 'target/hello/greeting.txt').exists() assert new File(basedir, 'build.log').text.contains('Hello, Ada!') ``` ## Wiring it (plugin POM) ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-invoker-plugin</artifactId> <version>3.x.x</version> <configuration> <projectsDirectory>src/it</projectsDirectory> <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo> <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath> <settingsFile>src/it/settings.xml</settingsFile> <postBuildHookScript>verify</postBuildHookScript> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>install</goal> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> ``` ## Why ITs matter for plugins Only a real build proves that: the descriptor was generated correctly, `@Parameter` injection works from actual POM XML and `-D`, `requiresDependencyResolution` populated artifacts, the goal bound to the right phase, and the goal interoperates with other plugins. Gate them behind a `run-its` profile so normal `mvn test` stays fast and CI runs them explicitly.
- Why does the invoker plugin install the plugin into an isolated local repo before running the ITs?So the sample projects resolve the exact version just built (via an isolated target/local-repo and custom settings.xml), not a previously released version sitting in the user's ~/.m2, guaranteeing tests exercise the code under test.
- How is invoker IT different from maven-plugin-testing-harness?The harness unit-tests the Mojo class in-process (fast, but bypasses the real descriptor, resolution, and lifecycle). Invoker runs a full real Maven build of sample projects, validating end-to-end behavior including descriptor generation and parameter injection.
saying these in an interview costs you the question
- Claiming unit tests alone fully validate a plugin (they skip the descriptor and real injection)
- Forgetting to install the plugin into a staging repo so ITs use a stale released version
- Running ITs in the default lifecycle so every build is slow
- Confusing maven-invoker-plugin with maven-failsafe-plugin (failsafe is for app ITs, not plugin sample-project ITs)