Sub-projects, tasks (doFirst/doLast) in multi-project build

I’ve been through the gradle manual and looked at the java multi-project example and I haven’t been able to figure out how to do what I’m imagining, or more importantly whether this is even a smart way to go about this.

My structure looks like this

app/
  build.gradle
  settings.gradle
  src/main/{java,resources}
  src/test/{java,resources}
  acceptance/
    build.gradle
    src/main/test/{java,resources}

app/src/test is where we’re placing JUnit/unit tests and app/acceptance/src/test is where we’re placing cucumber acceptance tests.

I’ve opted for a sub-project as we want classpath isolation, the acceptance project will only make HTTP requests against the an instance of the service from the top level project.

Note: I could use a Java source set but then we hit an issue in IntelliJ where it has only “main” and “test” classpaths and I want a third. I can’t budge on using IntelliJ because of work so I want to make the IDE experience as smooth as possible. A sub-project/module works well in IDEA.

My problem comes alongside a task defined in app/acceptance/build.gradle named cucumber. This task invokes Cucumber CLI via the javaexec tasks and this is fine. I would like the behaviour where if cucumber is invoked from the app/ dir, a doFirst and doLast is invoked to setup and teardown an instance of the app/.

I can run the app easily with gradle run (or gradle runShadow) – but I cannot get gradle to co-ordinate doing some setup and teardown in the root project when the cucumber task is called in the acceptance sub-project.

I attempted to put the following in app/build.gradle:

project(':acceptance') {
    cucumber {
        dependsOn test

        doFirst {
            println 'BEFORE ACCEPTANCE TESTS!'
        }

        doLast {
            println 'AFTER ACCEPTANCE TESTS!'
        }
    }
}

But I get the following error:

Could not find method cucumber() for arguments [build_fk50yayrqj9dxaqt2zuh3qia$_run_closure9_closure12@1665fa54] on root project 'app'.

The cucumber task is defined as follows (in app/acceptance/build.gradle):

task cucumber() {
    dependsOn assemble, compileTestJava

    description 'Invokes cucumber acceptance tests.'

    doLast {
        javaexec {
            workingDir = projectDir
            main = 'cucumber.api.cli.Main'
            classpath = configurations.testRuntime + sourceSets.test.output
            args = [
                '--plugin', 'pretty',
                '--plugin', 'html:build/cucumber',
                '--plugin', 'json:build/cucumber.json',
                '--plugin', 'junit:build/cucumber.xml',
                '--glue', 'app.cucumber',
                'src/test/resources']
        }
    }
}

Am I fundamentally misunderstanding something? Is this something I should look to do another way?

The desired outcome is a single command gradle <whatever> which: runs unit tests, builds app/, starts an instance of it and then invokes the cucumber task to run acceptance tests.

Have you considered using the cucumber plugin for Gradle instead, rather than rolling your own solution?