How do you test that your plugin fails the build correctly, and how do you assert on failure output?
answer
- buildAndFail() expects failure
- build() throws UnexpectedBuildFailure
- buildAndFail() throws UnexpectedBuildSuccess
- assert task outcome == FAILED
- assert output contains the message
basics
~10 sCall buildAndFail() instead of build(). It returns a BuildResult only when the build fails, otherwise it throws. Then assert the failing task's outcome is FAILED and that result.output contains your error message.
solid answer
~40 sTestKit has two terminal methods on `GradleRunner`: `build()` expects success and throws `UnexpectedBuildFailure` if the build fails; `buildAndFail()` expects failure and throws `UnexpectedBuildSuccess` if it unexpectedly passes. To test that your plugin rejects bad configuration or input, configure the test project to trigger the error, call `buildAndFail()`, then assert: the failing task's `outcome` is `TaskOutcome.FAILED`, and `result.output` contains your specific, actionable error message. This is how you lock in good error UX — verifying the user actually sees a helpful message, not just that *something* failed. It is the symmetric counterpart to success-path testing and pairs naturally with input-validation logic in tasks.
code
kotlin · 8 linesval result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments("deploy")
.withPluginClasspath()
.buildAndFail()
assertEquals(TaskOutcome.FAILED, result.task(":deploy")!!.outcome)
assertTrue(result.output.contains("Missing required 'targetEnv'"))go deeper
Know buildAndFail() is used to test that the build fails.
Use FAILED outcome plus output-content assertions; know both Unexpected* exceptions.
Design negative tests that lock in error messages as part of the plugin contract.
Establish team conventions for negative-path coverage and error-message quality across plugins.
## Two terminal methods `GradleRunner` ends a test in one of two ways: - **`build()`** — asserts the build *succeeds*. If it fails, TestKit throws `UnexpectedBuildFailure` (carrying the `BuildResult` so you can inspect it). - **`buildAndFail()`** — asserts the build *fails*. If it unexpectedly succeeds, TestKit throws `UnexpectedBuildSuccess`. Picking the right one makes the test self-documenting: the method name states the expectation. ## Testing the failure path Good plugins validate inputs and fail with clear messages. To prove that: 1. Set up the test project to violate a precondition (missing required property, invalid value, etc.). 2. Call `buildAndFail()`. 3. Assert the relevant task `outcome == FAILED`. 4. Assert `result.output` contains the human-readable message you promised users. ```kotlin val result = GradleRunner.create() .withProjectDir(dir) .withArguments("validateConfig") .withPluginClasspath() .buildAndFail() assertEquals(TaskOutcome.FAILED, result.task(":validateConfig")!!.outcome) assertTrue(result.output.contains("property 'apiUrl' must be set")) ``` ## Why assert on the message, not just failure A build can fail for many reasons. Asserting only "it failed" lets a misleading or wrong error pass the test. Asserting on the *content* of the message verifies the error UX — that a real user would understand what to fix. This treats error messages as part of the plugin's contract. ## Note on output capture By default failure output (stderr/stack traces) is included in `result.output`. You can also forward the runner's output to the test's own console for debugging with `forwardOutput()` or `forwardStdOutput(...)`.
- What happens if you call buildAndFail() but the build actually succeeds?TestKit throws UnexpectedBuildSuccess, failing your test — which is correct, because the expectation (failure) was not met.
- Why assert on output content rather than just that the build failed?Because a build can fail for the wrong reason. Asserting the specific message verifies your plugin's error UX and turns the error text into a tested contract.
saying these in an interview costs you the question
- Using build() and try/catching the failure instead of buildAndFail().
- Asserting only that it failed without checking the message — weak test.
- Confusing UnexpectedBuildFailure (from build()) with UnexpectedBuildSuccess (from buildAndFail()).