skip to content

TestKit GradleRunner

Driving real builds from a test with GradleRunner: a temporary project directory, arguments, the plugin classpath, and asserted task outcomes. Interviewers ask because it is the only way to test build logic end to end.

on this pageshow

questions

5

What is Gradle TestKit's GradleRunner, and how do you use it to functionally test a Gradle plugin?

level: juniorimportance: must knowfreq 55%

answer

  1. gradleTestKit() dependency
  2. create().withProjectDir().withArguments().build()
  3. BuildResult.output + task().outcome
  4. withPluginClasspath() + java-gradle-plugin
  5. real build, not in-memory Project

basics

~20 s

GradleRunner 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 lines
kotlin
val result = GradleRunner.create()
    .withProjectDir(tempDir)
    .withArguments("greeting")
    .withPluginClasspath()
    .build()

assertEquals(TaskOutcome.SUCCESS, result.task(":greeting")?.outcome)
assertTrue(result.output.contains("Hello"))

go deeper

for a junior

Name the API: GradleRunner.create().withProjectDir().withArguments().build(), and that it runs a real build returning a BuildResult.

for a middle

Explain withPluginClasspath() + java-gradle-plugin, asserting on output and task outcome, and using a JUnit @TempDir project.

for a senior

Contrast functional (TestKit) vs unit (ProjectBuilder) testing, when to choose each, and how to structure a maintainable functional-test fixture.

for a principal

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).

context

open as a page

How do you assert on the result of a TestKit build — what does BuildResult give you and what is TaskOutcome?

level: middleimportance: must knowfreq 45%

basics

~10 s

build() returns a BuildResult. You read result.output for console text and result.task(":path").outcome for a TaskOutcome enum like SUCCESS, UP_TO_DATE, FROM_CACHE, or SKIPPED, then assert on those.

open as a page

How do you write a TestKit test that asserts a build fails as expected, and what does buildAndFail() return?

level: middleimportance: should knowfreq 35%

basics

~20 s

Use buildAndFail() instead of build() when you expect failure. It runs the build, expects a non-zero result, and returns a BuildResult whose output contains the error so you can assert on the failure message and the failing task's FAILED outcome.

open as a page

What are the common pitfalls in TestKit functional tests around configuration-cache compatibility, test isolation, and output assertions, and how do you keep the suite reliable?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Give each test a fresh @TempDir project, isolate the TestKit/Gradle home so runs don't share state, drive output deterministically, and add --configuration-cache to runs to catch config-cache violations early. Avoid asserting on default-log noise.

open as a page

How do you control the Gradle version and the execution mode (debug / in-process vs daemon) for a TestKit run, and why does it matter?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use withGradleVersion("8.7") to test against a specific Gradle version, and withDebug(true) to run the build in the same JVM so you can set breakpoints. By default TestKit runs in a forked daemon at the runner's Gradle version.

open as a page