Gradle provides a robust mechanism for testing binary plugins.
Gradle’s TestKit allows you to programmatically execute synthetic builds that use the plugin under development, all within the plugin’s own build.
A well-tested plugin typically includes two levels of testing:
- 
Unit Tests
 - 
Functional Tests
 
The directory of our plugin looks as follows:
.
└── plugin
    ├── settings.gradle.kts
    ├── build.gradle.kts
    └── src
       ├── main
       │   └── java/org/example
       │       ├── FileSizeDiffTask.java
       │       ├── FileSizeDiffPlugin.java
       │       └── FileSizeDiffExtension.java
       ├── test
       │   └── java/org/example
       │       └── FileSizeDiffPluginTest.java
       └── functionalTest
           └── java/org/example
               └── FileSizeDiffPluginFunctionalTest.java
.
└── plugin
    ├── settings.gradle
    ├── build.gradle
    └── src
       ├── main
       │   └── java/org/example
       │       ├── FileSizeDiffTask.java
       │       ├── FileSizeDiffPlugin.java
       │       └── FileSizeDiffExtension.java
       ├── test
       │   └── java/org/example
       │       └── FileSizeDiffPluginTest.java
       └── functionalTest
           └── java/org/example
               └── FileSizeDiffPluginFunctionalTest.java
1. Unit Test
Unit tests validate the internal behavior of your plugin in a lightweight, isolated project. These tests simulate applying your plugin without needing a full Gradle execution.
They test:
- 
The plugin applies without errors
 - 
Tasks or extensions are registered correctly
 
The unit test for the filesizediff plugin looks as follows:
package org.example;
import org.gradle.testfixtures.ProjectBuilder;
import org.gradle.api.Project;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class FileSizeDiffPluginTest {
    @Test void pluginRegistersATask() {
        // Create a test project and apply the plugin
        Project project = ProjectBuilder.builder().build();
        project.getPlugins().apply("org.example.filesizediff");
        // Verify the result
        assertNotNull(project.getTasks().findByName("fileSizeDiff"));
    }
}
This test checks that the org.example.filesizediff plugin is applied and a fileSizeDiff task is added.
Unit tests are fast and useful for verifying the basic mechanics of your plugin logic.
2. Functional Test
Functional tests (also known as end-to-end plugin tests) use Gradle’s TestKit to launch real Gradle builds in a temporary directory.
This is how you verify your plugin works correctly in real-world usage.
They test:
- 
The plugin can be applied to a real build
 - 
The plugin task runs correctly and produces the expected output
 - 
Users can configure the plugin via the DSL
 
The functional test for the filesizediff plugin looks as follows:
package org.example;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class FileSizeDiffPluginFunctionalTest {
    // Temporary directory for each test, automatically cleaned up after the test run
    @TempDir
    File projectDir;
    // Helper to get reference to build.gradle in the temp project
    private File getBuildFile() {
        return new File(projectDir, "build.gradle");
    }
    // Helper to get reference to settings.gradle in the temp project
    private File getSettingsFile() {
        return new File(projectDir, "settings.gradle");
    }
    // Create minimal build and settings files before each test
    @BeforeEach
    void setup() throws IOException {
        // Empty settings.gradle
        writeString(getSettingsFile(), "");
        // Apply the plugin and configure the extension in build.gradle
        writeString(getBuildFile(), """
            plugins {
                id("org.example.filesizediff")
            }
            diff {
                file1 = file("a.txt")
                file2 = file("b.txt")
            }
        """
        );
    }
    // Test case: both input files have the same size (empty)
    @Test
    void canDiffTwoFilesOfTheSameSize() throws IOException {
        // Create empty file a.txt
        writeString(new File(projectDir, "a.txt"), "");
        // Create empty file b.txt
        writeString(new File(projectDir, "b.txt"), "");
        // Run the build with the plugin classpath and invoke the fileSizeDiff task
        BuildResult result = GradleRunner.create()
                .withProjectDir(projectDir)
                .withPluginClasspath()
                .withArguments("fileSizeDiff")
                .build();
        // Verify the output message and successful task result
        assertTrue(result.getOutput().contains("Files have the same size"));
        assertEquals(TaskOutcome.SUCCESS, result.task(":fileSizeDiff").getOutcome());
    }
    // Test case: first file is larger than second file
    @Test
    void canDiffTwoFilesOfDiffSize() throws IOException {
        // File a.txt has 7 bytes
        writeString(new File(projectDir, "a.txt"), "dsdsdad");
        // File b.txt is empty
        writeString(new File(projectDir, "b.txt"), "");
        // Run the build and invoke the plugin task
        BuildResult result = GradleRunner.create()
                .withProjectDir(projectDir)
                .withPluginClasspath()
                .withArguments("fileSizeDiff")
                .build();
        // Verify the output message indicates a.txt is larger
        assertTrue(result.getOutput().contains("a.txt was larger: 7 bytes"));
        assertEquals(TaskOutcome.SUCCESS, result.task(":fileSizeDiff").getOutcome());
    }
    // Helper method to write string content to a file
    private static void writeString(File file, String string) throws IOException {
        Files.writeString(file.toPath(), string);
    }
}
GradleRunner is the core API provided by TestKit for executing builds in a test environment.
GradleRunner simulates a Gradle invocation.
Each test creates a temporary project with a build.gradle file, input files, and runs the plugin task using GradleRunner.
Unlike unit tests in src/test/java that test individual classes, functional tests live in src/functionalTest/java and verify that your plugin behaves correctly in a real build.
Gradle doesn’t automatically recognize custom test source sets, so you need to declare a functionalTest source set and configure a task to run it.
Fortunately, gradle init can scaffold this setup for you:
plugins {
    `java-gradle-plugin`    (1)
}
group = "org.example"   (3)
version = "1.0.0"
repositories {
    mavenCentral()
}
dependencies {  (2)
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
gradlePlugin {  (3)
    plugins {
        create("filesizediff") {
            id = "org.example.filesizediff"
            implementationClass = "org.example.FileSizeDiffPlugin"
        }
    }
}
// Created by gradle init
// Add a source set for the functional test suite
val functionalTestSourceSet = sourceSets.create("functionalTest") {
}
configurations["functionalTestImplementation"].extendsFrom(configurations["testImplementation"])
configurations["functionalTestRuntimeOnly"].extendsFrom(configurations["testRuntimeOnly"])
// Add a task to run the functional tests
val functionalTest by tasks.registering(Test::class) {
    description = "Runs functional tests."
    group = "verification"
    testClassesDirs = functionalTestSourceSet.output.classesDirs
    classpath = functionalTestSourceSet.runtimeClasspath
    useJUnitPlatform()
}
gradlePlugin.testSourceSets.add(functionalTestSourceSet)
tasks.named<Task>("check") {
    // Run the functional tests as part of `check`
    dependsOn(functionalTest)
}
tasks.named<Test>("test") {
    // Use JUnit Jupiter for unit tests.
    useJUnitPlatform()
}
plugins {
    id('java-gradle-plugin')    (1)
}
group = "org.example"   (3)
version = "1.0.0"
repositories {
    mavenCentral()
}
dependencies {  (2)
    testImplementation('org.junit.jupiter:junit-jupiter-api:5.10.0')
    testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.10.0')
    testRuntimeOnly('org.junit.platform:junit-platform-launcher')
}
gradlePlugin {  (3)
    plugins {
        filesizediff {
            id = 'org.example.filesizediff'
            implementationClass = 'org.example.FileSizeDiffPlugin'
        }
    }
}
// Created by gradle init
// Add a source set for the functional test suite
sourceSets {
    functionalTest {
    }
}
configurations {
    functionalTestImplementation.extendsFrom(testImplementation)
    functionalTestRuntimeOnly.extendsFrom(testRuntimeOnly)
}
// Add a task to run the functional tests
tasks.register('functionalTest', Test) {
    description = 'Runs functional tests.'
    group = 'verification'
    testClassesDirs = sourceSets.functionalTest.output.classesDirs
    classpath = sourceSets.functionalTest.runtimeClasspath
    useJUnitPlatform()
}
// Include functional test source set in plugin validation
gradlePlugin.testSourceSets.add(sourceSets.functionalTest)
// Run functional tests as part of check
tasks.named('check', Task) {
    dependsOn tasks.named('functionalTest')
}
// Use JUnit Platform for unit tests
tasks.named('test', Test) {
    useJUnitPlatform()
}
Consumer Project
You can optionally create a consumer project that uses your plugin:
.
├── settings.gradle.kts // Include custom plugin
├── build.gradle.kts    // Applies custom plugin
│
└── plugin
    ├── settings.gradle.kts
    ├── build.gradle.kts
    └── src
       ...
.
├── settings.gradle.kts // Include custom plugin
├── build.gradle.kts    // Applies custom plugin
│
└── plugin
    ├── settings.gradle
    ├── build.gradle
    └── src
       ...
First you can point to the plugin as an included build in the consumer settings file:
rootProject.name = "consumer"
includeBuild("plugin")
rootProject.name = 'consumer'
includeBuild("plugin")
Then in the build file of the consumer project, you apply the plugin and test it:
plugins {
    id("org.example.filesizediff")
}
diff {
    file1 = file("a.txt")
    file2 = file("b.txt")
}
plugins {
    id("org.example.filesizediff")
}
diff {
    file1 = file('a.txt')
    file2 = file('b.txt')
}
In the consumer project, you can create dummy a.txt and b.txt files and run ./gradlew fileSizeDiff:
$ ./gradlew fileSizeDiff
> Task :fileSizeDiff
Files have the same size: 0 bytes
Wrote diff result to /home/user/gradle/samples/build/diff-result.txt
BUILD SUCCESSFUL in 0s
Next Step: Learn how to publish Binary Plugins >>