What is Gradle TestKit's GradleRunner, and how do you use it to functionally test a Gradle plugin?
answer
- gradleTestKit() dependency
- create().withProjectDir().withArguments().build()
- BuildResult.output + task().outcome
- withPluginClasspath() + java-gradle-plugin
- real build, not in-memory Project
basics
~20 sGradleRunner is the TestKit entry point that runs a real Gradle build against a temporary project directory. You create it, point it at a project dir, pass arguments, and call build() to execute and inspect the result.
solid answer
~30 s`GradleRunner` (from `gradleTestKit()`) lets you run a **real Gradle build in-process** against a throwaway project to verify your plugin end-to-end, rather than mocking internals. The typical chain is `GradleRunner.create().withProjectDir(tempDir).withArguments("myTask").withPluginClasspath().build()`. You write a `build.gradle(.kts)` and any sources into a JUnit temp directory, apply your plugin, then assert on the returned `BuildResult` — its `output` string and per-task `outcome` (e.g. `task(":myTask").outcome == SUCCESS`). `withPluginClasspath()` injects the plugin-under-test onto the build's classpath (paired with the `java-gradle-plugin` plugin, which generates the metadata). Use `build()` for expected-success and `buildAndFail()` for expected-failure scenarios. This is functional/black-box testing of the plugin's externally observable behavior.
code
kotlin · 8 linesval result = GradleRunner.create()
.withProjectDir(tempDir)
.withArguments("greeting")
.withPluginClasspath()
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":greeting")?.outcome)
assertTrue(result.output.contains("Hello"))go deeper
Name the API: GradleRunner.create().withProjectDir().withArguments().build(), and that it runs a real build returning a BuildResult.
Explain withPluginClasspath() + java-gradle-plugin, asserting on output and task outcome, and using a JUnit @TempDir project.
Contrast functional (TestKit) vs unit (ProjectBuilder) testing, when to choose each, and how to structure a maintainable functional-test fixture.
Position TestKit in a plugin's overall test strategy and CI: coverage of cross-version compatibility, fixture reuse, and balancing slow functional tests against fast unit tests.
## What TestKit is Gradle **TestKit** is the official toolkit for **functional testing** of Gradle plugins and build logic. Unit tests with `ProjectBuilder` create an in-memory `Project` but never actually run a build; TestKit instead executes a **real Gradle build** against a real (temporary) project directory, so you test the plugin the way a user would experience it — tasks register, configure, and execute for real. You get TestKit on the test classpath via the `gradleTestKit()` dependency notation: ```kotlin dependencies { testImplementation(gradleTestKit()) testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") } ``` ## The GradleRunner API `GradleRunner` is the central class. The fluent chain: - **`GradleRunner.create()`** — builds a runner instance. - **`.withProjectDir(File)`** — the root of the build to run; you write `settings.gradle(.kts)`, `build.gradle(.kts)`, and sources here, usually a JUnit `@TempDir`. - **`.withArguments("clean", "myTask", "--stacktrace")`** — the command-line args Gradle receives. - **`.withPluginClasspath()`** — injects the plugin-under-test's classpath so `plugins { id("my.plugin") }` (or `apply`) resolves without publishing. - **`.build()`** — runs and **expects success**; returns a `BuildResult`. - **`.buildAndFail()`** — runs and **expects a failed build**; also returns `BuildResult`. ## Inspecting the BuildResult `BuildResult` exposes: - `getOutput()` — the full console output (assert on log/println text). - `task(":path")?.outcome` — the `TaskOutcome` enum: `SUCCESS`, `FAILED`, `UP_TO_DATE`, `SKIPPED`, `NO_SOURCE`, `FROM_CACHE`. - `tasks(TaskOutcome.SUCCESS)` — all tasks with a given outcome. ## withPluginClasspath and java-gradle-plugin For `withPluginClasspath()` to work automatically, apply the **`java-gradle-plugin`** plugin in the plugin project. It generates a plugin-under-test metadata file the runner reads to know the classpath. Without it you must call `withPluginClasspath(files)` and pass the classpath explicitly. ## A full example ```kotlin @Test fun `greeting task prints message`(@TempDir projectDir: File) { projectDir.resolve("settings.gradle.kts").writeText("") projectDir.resolve("build.gradle.kts").writeText( """ plugins { id("com.example.greeting") } """.trimIndent() ) val result = GradleRunner.create() .withProjectDir(projectDir) .withArguments("greeting") .withPluginClasspath() .build() assertTrue(result.output.contains("Hello")) assertEquals(TaskOutcome.SUCCESS, result.task(":greeting")?.outcome) } ``` This runs a genuine build, applies your plugin, executes `:greeting`, and asserts on both output and outcome — verifying the plugin's real, user-facing behavior.
- What does withPluginClasspath() do, and what must be on the plugin project for it to work without arguments?It injects the plugin-under-test's classes onto the test build's classpath so the plugin id resolves without publishing. The no-arg form needs the `java-gradle-plugin` plugin applied, which generates the plugin-under-test metadata the runner reads.
- How is TestKit different from ProjectBuilder unit tests?ProjectBuilder creates an in-memory `Project` for fast, unit-style checks of configuration logic but never runs a build. TestKit executes a real Gradle build end-to-end, so it verifies task execution and user-facing behavior at the cost of being slower.
saying these in an interview costs you the question
- Claiming GradleRunner mocks Gradle internals — it runs a real build.
- Confusing GradleRunner with ProjectBuilder (the in-memory unit-test API).