Skip to content

Commit 2a205d2

Browse files
committed
Extend the top interaction scope to cleanup methods
1 parent dd81cb1 commit 2a205d2

56 files changed

Lines changed: 132 additions & 53 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/interaction_based_testing.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ This makes sure that `subscriber` receives `"message1"` during execution of the
444444
and `"message2"` during execution of the second `when:` block.
445445

446446
Interactions declared outside a `then:` block are active from their declaration until the end of the
447-
containing feature method.
447+
containing feature method plus an eventually existing `cleanup` method.
448448

449449
Interactions are always scoped to a particular feature method. Hence they cannot be declared in a static method,
450450
`setupSpec` method, or `cleanupSpec` method. Likewise, mock objects should not be stored in static or `@Shared`

docs/release_notes.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ include::include.adoc[]
1919
=== Breaking Changes
2020

2121
* Mock/Stub checks on `Comparable<T>` with `T` being something other than `Object` now compare using the java identity hash code instead of always being equal spockIssue:2352[]
22+
* Interactions outside a `then:` block are now still active in an eventually existing `cleanup` method.
23+
If the interaction contains a lower cardinality, this is still checked at the end of the feature method.
24+
Upper cardinality is still relevant in the `cleanup` method, so could cause test failures where previously simply the default response of the mock object was used without cardinality check and could now cause previously working tests to fail.
25+
This enables `cleanup` methods to get stubbed responses from mock objects which previously was only possible by using a custom default response.
26+
If an interaction with stubbed response is present, the default response is also no longer used in the `cleanup` method because the interaction's stubbed response is used now, which could also cause existing tests to fail.
27+
spockIssue:616[]
2228

2329
== 2.4 (2025-12-11)
2430

spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public class AstNodeCache {
107107
public final MethodNode MockController_LeaveScope =
108108
MockController.getDeclaredMethods(org.spockframework.mock.runtime.MockController.LEAVE_SCOPE).get(0);
109109

110+
public final MethodNode MockController_VerifyLastScope =
111+
MockController.getDeclaredMethods(org.spockframework.mock.runtime.MockController.VERIFY_LAST_SCOPE).get(0);
112+
110113
public final MethodNode SpecificationContext_GetMockController =
111114
SpecificationContext.getDeclaredMethods(org.spockframework.runtime.SpecificationContext.GET_MOCK_CONTROLLER).get(0);
112115

spock-core/src/main/java/org/spockframework/compiler/SpecRewriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ public void visitMethodAgain(Method method) {
410410

411411
// for global required interactions
412412
if (method instanceof FeatureMethod) {
413-
method.getStatements().add(createMockControllerCall(nodeCache.MockController_LeaveScope));
413+
method.getStatements().add(createMockControllerCall(nodeCache.MockController_VerifyLastScope));
414414
}
415415

416416
if (method instanceof VerifyAllMethod) {

spock-core/src/main/java/org/spockframework/mock/TooFewInvocationsError.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public TooFewInvocationsError(List<IMockInteraction> interactions, List<IMockInv
3737
Assert.notNull(interactions);
3838
Assert.that(interactions.size() > 0);
3939
this.interactions = interactions;
40-
this.unmatchedInvocations = unmatchedInvocations;
40+
this.unmatchedInvocations = new ArrayList<>(unmatchedInvocations);
4141
}
4242

4343
@Override

spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.spockframework.mock.runtime;
1818

1919
import org.spockframework.mock.*;
20-
import org.spockframework.util.Checks;
20+
import org.spockframework.util.Assert;
2121
import org.spockframework.util.ExceptionUtil;
2222

2323
import java.util.*;
@@ -110,6 +110,14 @@ public synchronized void leaveScope() {
110110
scope.verifyInteractions();
111111
}
112112

113+
public static final String VERIFY_LAST_SCOPE = "verifyLastScope";
114+
115+
public synchronized void verifyLastScope() {
116+
throwAnyPreviousError();
117+
Assert.that(scopes.size() == 1, () -> "There should be exactly one scope left, but there were " + scopes.size());
118+
scopes.getFirst().verifyInteractions();
119+
}
120+
113121
private void throwAnyPreviousError() {
114122
if (!errors.isEmpty()) throw errors.get(0);
115123
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.spockframework.smoke.mock
18+
19+
import org.spockframework.runtime.ConditionNotSatisfiedError
20+
import spock.lang.FailsWith
21+
import spock.lang.Issue
22+
import spock.lang.Specification
23+
24+
import java.util.function.Function
25+
26+
@Issue("https://github.com/spockframework/spock/issues/616")
27+
class GlobalInteractionsInCleanup extends Specification {
28+
def mock = Mock(Function)
29+
def mock2 = Mock(Function) {
30+
apply("a") >> { "c" }
31+
}
32+
def mock3 = Mock(Function)
33+
34+
def setup() {
35+
mock.apply("a") >> { "b" }
36+
}
37+
38+
def cleanup() {
39+
verifyAll {
40+
mock.apply("a") == "b"
41+
mock2.apply("a") == "c"
42+
mock3.apply("a") == "d"
43+
}
44+
}
45+
46+
def "run a successful test"() {
47+
given:
48+
mock3.apply("a") >> { "d" }
49+
50+
expect:
51+
true
52+
}
53+
54+
@FailsWith(ConditionNotSatisfiedError)
55+
def "run a failing test"() {
56+
given:
57+
mock3.apply("a") >> { "d" }
58+
59+
expect:
60+
false
61+
}
62+
}

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/Primitive_types_are_used_in_AST_transformation.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public class apackage/TestSpec extends spock/lang/Specification implements groov
181181
LDC Lorg/spockframework/mock/runtime/MockController;.class
182182
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
183183
CHECKCAST org/spockframework/mock/runtime/MockController
184-
INVOKEVIRTUAL org/spockframework/mock/runtime/MockController.leaveScope ()V
184+
INVOKEVIRTUAL org/spockframework/mock/runtime/MockController.verifyLastScope ()V
185185
ACONST_NULL
186186
POP
187187
L18

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_can_render_everything.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class apackage.ASpec extends spock.lang.Specification {
1010
org.spockframework.runtime.SpockRuntime.callBlockEntered(this, 0)
1111
java.lang.Object nothing = null
1212
org.spockframework.runtime.SpockRuntime.callBlockExited(this, 0)
13-
this.getSpecificationContext().getMockController().leaveScope()
13+
this.getSpecificationContext().getMockController().verifyLastScope()
1414
}
1515

1616
}

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/AstSpec/astToSourceFeatureBody_renders_only_methods_and_its_annotation_by_default.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public void $spock_feature_0_0() {
99
org.spockframework.runtime.SpockRuntime.callBlockEntered(this, 0)
1010
java.lang.Object nothing = null
1111
org.spockframework.runtime.SpockRuntime.callBlockExited(this, 0)
12-
this.getSpecificationContext().getMockController().leaveScope()
12+
this.getSpecificationContext().getMockController().verifyLastScope()
1313
}
1414
/*--------- end::snapshot[] ---------*/
1515
}

0 commit comments

Comments
 (0)