Allow declaring mock interactions in @SelfType(Specification) traits#2377
Conversation
Add a third external-interaction mechanism: a Groovy trait annotated with `@groovy.transform.SelfType(Specification)`. The self-type guarantees that `this` is a Specification wherever the trait is mixed in, so the trait's methods may declare interactions and create mocks exactly as inside a spec, becoming reusable helpers that any spec inherits by implementing the trait. SpockTransform (a global transform) runs before Groovy's trait transform, so it sees the trait as a plain class with instance-method bodies and rewrites them against `this`. Groovy's trait transform then relocates each body into the `$Trait$Helper` and rewrites `this` to the `$self` parameter (the real spec instance at runtime). Detection is `Traits.isTrait` plus a `@SelfType` value assignable to `Specification`; the trait rewriter reuses the external SPI with `guardSpecNotNull=false` (the receiver is non-null by construction). The self-type may be a subclass of `Specification`; static trait methods cannot declare interactions (no instance to reach the spec through). Adds the trait detection and `processSpecificationTrait` in `SpockTransform`, the `SelfType` AST node, behaviour tests (cardinality, stubbing, mock creation, self-type subclass, static-method error), and an AST snapshot showing the relocated `$self`-based output.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## external-mock-interactions-2-interactions-annotation #2377 +/- ##
==========================================================================================
- Coverage 82.53% 82.51% -0.03%
- Complexity 4936 4940 +4
==========================================================================================
Files 481 481
Lines 15454 15478 +24
Branches 1990 1999 +9
==========================================================================================
+ Hits 12755 12771 +16
- Misses 1993 1998 +5
- Partials 706 709 +3
🚀 New features to boost your workflow:
|
✅ All tests passed ✅🏷️ Commit: 02937a1 Learn more about TestLens at testlens.app. |
AndreasTu
left a comment
There was a problem hiding this comment.
@leonard84 The ideas of the PRs look good to me so far, just some minor findings.
Should I continue to review the other ones, or do you want to fixup the first ones first?
| [[SpecificationTrait]] | ||
| ==== @SelfType(Specification) traits | ||
|
|
||
| A Groovy trait annotated with `@groovy.transform.SelfType(Specification)` is, by construction, a fragment of a `Specification`: `this` is guaranteed to be a spec wherever the trait is mixed in. |
There was a problem hiding this comment.
I think it would be nice, if there is a sentence: "Why I should use that feature?" at the start. The current sentence is very technical and does not answer fully, whne to use it, when skimming through the documentation.
| * Add support for `final` local variables in `where:` blocks, declared at their beginning and evaluated once per feature, scoped to the where-block spockIssue:138[] | ||
| * Improve `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions spockPull:2315[] | ||
| * Allow declaring mock interactions outside a `Specification` via the `MockInteractionSupport` interface (which also supports mock creation) and the `@Interactions` method annotation, see <<interaction_based_testing.adoc#external-interactions,Declaring Interactions Outside Specifications>> | ||
| * Allow declaring mock interactions outside a `Specification` via the `MockInteractionSupport` interface, the `@Interactions` method annotation, and `@SelfType(Specification)` traits, see <<interaction_based_testing.adoc#external-interactions,Declaring Interactions Outside Specifications>> |
| */ | ||
| boolean isSpecificationTrait(ClassNode clazz) { | ||
| return Traits.isTrait(clazz) && selfTypeIsSpecification(clazz); | ||
| } |

Add a third external-interaction mechanism: a Groovy trait annotated with
@groovy.transform.SelfType(Specification). The self-type guarantees thatthisis a Specification wherever the trait is mixed in, so the trait's methodsmay declare interactions and create mocks exactly as inside a spec, becoming
reusable helpers that any spec inherits by implementing the trait.
SpockTransform (a global transform) runs before Groovy's trait transform, so it
sees the trait as a plain class with instance-method bodies and rewrites them
against
this. Groovy's trait transform then relocates each body into the$Trait$Helperand rewritesthisto the$selfparameter (the real specinstance at runtime). Detection is
Traits.isTraitplus a@SelfTypevalueassignable to
Specification; the trait rewriter reuses the external SPI withguardSpecNotNull=false(the receiver is non-null by construction). Theself-type may be a subclass of
Specification; static trait methods cannotdeclare interactions (no instance to reach the spec through).
Adds the trait detection and
processSpecificationTraitinSpockTransform, theSelfTypeAST node, behaviour tests (cardinality, stubbing, mock creation,self-type subclass, static-method error), and an AST snapshot showing the
relocated
$self-based output.