# Part 6: Writing a Functional Test

version 9.7.0-20260608020155+0000

Learn how to write a functional test to verify your plugin’s behavior in a real-world scenario.

In this section, you’ll:

- Understand the difference between unit and functional tests.

- Learn how to use Gradle’s TestKit for end-to-end testing.

- Update the default functional test to verify your plugin.

- Run the functional test and confirm your plugin works as expected.

## Step 0: Before you Begin

1. You initialized your plugin in part 1.

2. You added an extension to your plugin in part 2.

3. You created a custom task in part3.

4. You wrote a unit test in part 4.

5. You added a dataflow action to your plugin in part 2.

## Step 1: About Functional Tests

While a unit test checks a single class in isolation, a functional test verifies that your plugin works correctly in a real Gradle build.

Gradle provides a powerful testing framework called TestKit. It allows you to programmatically:

- Spin up a real Gradle process.

- Create a temporary, isolated project directory.

- Run a build in that directory with a specific set of tasks and arguments.

- Verify the build’s output, status, and side effects (like files being created).

The `gradle init` command created a sample functional test for you:

It’s available at `src/functionalTest/kotlin/org/example/PluginTutorialPluginFunctionalTest.kt`.

It’s available at `src/functionalTest/groovy/org/example/PluginTutorialPluginFunctionalTest.groovy`.

It uses the `GradleRunner` class to execute a build and then asserts that the build’s output contains the expected "Hello World" message. We’re going to re-use this same structure for our test.

## Step 2: Update the Test

First, let’s update the name of the test file to match our plugin.

Rename the file `PluginTutorialPluginFunctionalTest.kt` to `SlackPluginFunctionalTest.kt`

Rename the file `PluginTutorialPluginFunctionalTest.groovy` to `SlackPluginFunctionalTest.groovy`.

Now, update the test file to look like this:

plugin/src/functionalTest/kotlin/org/example/SlackPluginFunctionalTest.kt

    package org.example

    import java.io.File
    import kotlin.test.assertTrue
    import kotlin.test.Test
    import org.gradle.testkit.runner.GradleRunner
    import org.junit.jupiter.api.io.TempDir

    class SlackPluginFunctionalTest {

        @field:TempDir
        lateinit var projectDir: File

        private val buildFile by lazy { projectDir.resolve("build.gradle") }
        private val settingsFile by lazy { projectDir.resolve("settings.gradle") }

        @Test fun `can run task`() {
            // Set up the test build
            settingsFile.writeText("")
            buildFile.writeText("""
                plugins {
                    id('org.example.slack')
                }
                slack {
                    token.set(System.getenv("SLACK_TOKEN"))
                    channel.set("#social")
                    message.set("Hello from Gradle!")
                }
            """.trimIndent()
            )

            // Run the build
            val runner = GradleRunner.create()
            runner.forwardOutput()
            runner.withPluginClasspath()
            runner.withProjectDir(projectDir)
            val result = runner.build()

            // Verify the result
            assertTrue(result.output.contains("Slack message sent successfully"))
        }
    }

plugin/src/functionalTest/groovy/org/example/SlackPluginFunctionalTest.groovy

    package org.example

    import spock.lang.Specification
    import spock.lang.TempDir
    import org.gradle.testkit.runner.GradleRunner

    class SlackPluginFunctionalTest extends Specification {
        @TempDir
        private File projectDir

        private getBuildFile() {
            new File(projectDir, "build.gradle")
        }

        private getSettingsFile() {
            new File(projectDir, "settings.gradle")
        }

        def "can run task"() {
            given:
            settingsFile << ""
            buildFile << """
                plugins {
                    id('org.example.slack')
                }
                slack {
                    token.set(System.getenv("SLACK_TOKEN"))
                    channel.set('#social')
                    message.set('Hello from Gradle!')
                }
            """

            when:
            def runner = GradleRunner.create()
            runner.forwardOutput()
            runner.withPluginClasspath()
            runner.withProjectDir(projectDir)
            def result = runner.build()

            then:
            result.output.contains("Slack message sent successfully")
        }
    }

This functional test does the following:

1. Creates a temporary test project (`@TempDir`), complete with `settings.gradle` and a `build.gradle` file.

2. Applies the Slack plugin and configures it with a token and channel. The token is retrieved from an environment variable, just like a real-world setup.

3. Runs the build using `GradleRunner`, with our custom task `sendTestSlackMessage` as the argument.

4. Verifies the result by asserting that the build output contains `"Slack message sent successfully"`.

## Step 3: Check out the Functional Test Build Logic

Before you run the test, it’s good to understand how `gradle init` configured our project to support functional tests.

Look at the `build.gradle(.kts)` file and notice the section related to functional tests:

plugin/build.gradle.kts

    // 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 = tasks.register<Test>("functionalTest") {
        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()
    }

plugin/build.gradle

    // Add a source set for the functional test suite
    sourceSets {
        functionalTest {
        }
    }

    configurations.functionalTestImplementation.extendsFrom(configurations.testImplementation)
    configurations.functionalTestRuntimeOnly.extendsFrom(configurations.testRuntimeOnly)

    // Add a task to run the functional tests
    tasks.register('functionalTest', Test) {
        testClassesDirs = sourceSets.functionalTest.output.classesDirs
        classpath = sourceSets.functionalTest.runtimeClasspath
        useJUnitPlatform()
    }

    gradlePlugin.testSourceSets.add(sourceSets.functionalTest)

    tasks.named('check') {
        // Run the functional tests as part of `check`
        dependsOn(tasks.functionalTest)
    }

    tasks.named('test') {
        // Use JUnit Jupiter for unit tests.
        useJUnitPlatform()
    }

This configuration sets up a dedicated `functionalTest` source set and task, which keeps our functional tests separate from our unit tests. This makes our test suites more organized and reliable.

## Step 4: Run the Functional Test

The functional test uses an environment variable named `SLACK_TOKEN`. Before you run the test, you must set this variable in your terminal.

    $ export SLACK_TOKEN="xoxb-..."

Now you can run the `functionalTest` task to execute your test.

    $ ./gradlew :functionalTest

    > Task :plugin:functionalTest

    BUILD SUCCESSFUL in 4s
    6 actionable tasks: 1 executed, 5 up-to-date

If everything is configured correctly, you should see a message confirming a successful build, and a test message should have been sent to your Slack workspace!

You can also run the `check` task, which executes both unit and functional tests.

Next Step: Use a Consumer Project >>