From 3c5beabb6a02d4187349dda237d1390187206de6 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 1 Nov 2025 11:40:57 +0100 Subject: [PATCH 1/6] Fix SpyStatic() with an interaction closure throws NullPointerException The usage of SpyStatic(Type){} had thrown an NullPointerException, because the closure got automatically converted into IMockMakerSettings. --- docs/release_notes.adoc | 1 + .../mock/runtime/MockMakerRegistry.java | 7 +++ .../spockframework/runtime/SpecInternals.java | 13 +++- .../java/spock/mock/IMockMakerSettings.java | 4 ++ .../src/main/java/spock/mock/MockingApi.java | 27 ++++++++- .../mockito/MockitoStaticMocksSpec.groovy | 59 +++++++++++++++++++ .../mock/MockingApiInvalidUsageSpec.groovy | 3 +- 7 files changed, 109 insertions(+), 5 deletions(-) diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc index f1fc669dd6..a6c95412e1 100644 --- a/docs/release_notes.adoc +++ b/docs/release_notes.adoc @@ -112,6 +112,7 @@ spockPull:2112[] * Fix filter blocks with shared fields and derived data variables spockPull:2088[] * Fix combined labels with comments being ignored spockPull:2121[] * Fix boxed Boolean `is` getter methods not properly mocked in Groovy <= 3 spockIssue:2131[] +* Fix `SpyStatic()` with an interaction closure throws NullPointerException spockPull:2254[] Thanks to all the contributors to this release: Andreas Turban, Björn Kautler, Christoph Loy, Marcin Zajączkowski, Pavlo Shevchenko diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java b/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java index 53e0e9ee3a..7e49aa77e4 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java @@ -16,11 +16,13 @@ package org.spockframework.mock.runtime; +import groovy.lang.Closure; import org.spockframework.mock.CannotCreateMockException; import org.spockframework.mock.IMockObject; import org.spockframework.mock.ISpockMockObject; import org.spockframework.mock.runtime.IMockMaker.IStaticMock; import org.spockframework.mock.runtime.IMockMaker.MockMakerCapability; +import org.spockframework.runtime.GroovyRuntimeUtil; import org.spockframework.util.InternalSpockError; import org.spockframework.util.Nullable; import org.spockframework.util.ThreadSafe; @@ -28,6 +30,7 @@ import spock.mock.MockMakerId; import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -145,6 +148,10 @@ public T makeMockInternal(IMockCreationSettings settings, BiFunction T createMockImpl(Specification specification, String inferred } public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType) { - createStaticMockImpl(specification, MockNature.SPY, specifiedType, null); + createStaticMockImpl(specification, MockNature.SPY, specifiedType, null, null); + } + + public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType, Closure closure) { + createStaticMockImpl(specification, MockNature.SPY, specifiedType, null, closure); } public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType, IMockMakerSettings mockMakerSettings) { - createStaticMockImpl(specification, MockNature.SPY, specifiedType, mockMakerSettings); + createStaticMockImpl(specification, MockNature.SPY, specifiedType, mockMakerSettings, null); } - private static void createStaticMockImpl(Specification specification, MockNature nature, Class specifiedType, @Nullable IMockMakerSettings mockMakerSettings) { + private static void createStaticMockImpl(Specification specification, MockNature nature, Class specifiedType, @Nullable IMockMakerSettings mockMakerSettings, @Nullable Closure closure) { if (specifiedType == null) { throw new InvalidSpecException("The type must not be null."); } @@ -349,5 +353,8 @@ private static void createStaticMockImpl(Specification specification, MockNature options = Collections.singletonMap("mockMaker", mockMakerSettings); } createStaticMock(specification, specifiedType, nature, options); + if (closure != null) { + GroovyRuntimeUtil.invokeClosure(closure, specifiedType); + } } } diff --git a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java index 9806e93bb2..cc588ce539 100644 --- a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java +++ b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java @@ -16,6 +16,7 @@ *

If you provide a method, you can define your settings in a typesafe manner for the user, * e.g. with a {@link Closure} parameter to configure the mock. * + * @author Andreas Turban * @since 2.4 */ @Beta @@ -41,5 +42,8 @@ public String toString() { }; } + /** + * @return the {@link MockMakerId} to use, must not be {@code null}. + */ MockMakerId getMockMakerId(); } diff --git a/spock-core/src/main/java/spock/mock/MockingApi.java b/spock-core/src/main/java/spock/mock/MockingApi.java index b4d081638c..b9e0f0cb48 100644 --- a/spock-core/src/main/java/spock/mock/MockingApi.java +++ b/spock-core/src/main/java/spock/mock/MockingApi.java @@ -1643,6 +1643,32 @@ public void SpyStatic(Class type) { throw invalidMockCreation(); } + /** + * Creates a thread-local spy for all static methods of the passed type. + *

+ * Example: + * + *

+   *   SpyStatic(Person){
+   *      Person.staticMethod() >> "result"
+   *   }
+   * 
+ * + *

Please use the class name as a prefix, instead of relying on {@code it} from Groovy.

+ * + *

If you want to activate the static mocks on a different {@code Thread}, + * please call {@link #runWithThreadAwareMocks(Runnable)} on the different {@code Thread}. + * + * @param type the type of which the static methods shall be spied + * @param interactions a description of the static spy's interactions + */ + @Beta + public void SpyStatic(Class type, Closure interactions //Note: We can't specify here @ClosureParams or @DelegatesTo because there is no syntax to express that it means the Class object + ) { + + throw invalidMockCreation(); + } + /** * Creates a thread-local spy for all static methods of the passed type. *

@@ -1663,7 +1689,6 @@ public void SpyStatic(Class type, IMockMakerSettings mockMakerSettings) { throw invalidMockCreation(); } - /** * Runs the code with the thread-aware mocks activated on the current {@link Thread}. * diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index e17b8d386b..62aa6bf0ea 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -25,6 +25,7 @@ import org.spockframework.runtime.InvalidSpecException import spock.lang.Issue import spock.lang.Shared import spock.lang.Specification +import spock.mock.IMockMakerSettings import spock.mock.MockMakers import java.util.concurrent.Callable @@ -491,6 +492,64 @@ class MockitoStaticMocksSpec extends Specification { !StaticClass.staticVarargsMethod("test2") } + def "SpyStatic with closure shall not lead an exception"() { + when: + SpyStatic(StaticClass) { + + } + + then: + noExceptionThrown() + } + + def "SpyStatic with closure can specify interactions"() { + given: + SpyStatic(StaticClass) { + StaticClass.staticVarargsMethod("test") >> true + } + + expect: + StaticClass.staticVarargsMethod("test") + } + + def "SpyStatic with closure could use it instead of ClassName"() { + given: + SpyStatic(StaticClass) { + //Note: This does not have code completion support. + it.staticVarargsMethod("test") >> true + } + + expect: + StaticClass.staticVarargsMethod("test") + } + + def "SpyStatic with closure could use no prefix instead of ClassName"() { + given: + SpyStatic(StaticClass) { + //Note: This does not have code completion support. + staticVarargsMethod("test") >> true + } + + expect: + StaticClass.staticVarargsMethod("test") + } + + /** + * The IMockMakerSettings has only a single method, so it could be converted from a Groovy closure automatically. + * This shall check that the API produces a nice error to the user in such cases. + */ + def "SpyStatic with closure casted as IMockMakerSettings shall produce nice error message"() { + when: + SpyStatic(StaticClass, { + + } as IMockMakerSettings) + + then: + def ex = thrown(CannotCreateMockException) + ex.message.startsWith( + "Cannot create mock for class $StaticClass.name because the MockMakerSettings returned the invalid ID 'null'. Please check that a closure did not get accidentally casted to IMockMakerSettings. The settings object was ") + } + static class StaticClass { String instanceMethod() { diff --git a/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy b/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy index f5676a8d3f..23b6e50b96 100644 --- a/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy +++ b/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy @@ -95,6 +95,7 @@ class MockingApiInvalidUsageSpec extends Specification { "GroovySpy" | [CLOSURE] "SpyStatic" | [Runnable] - "SpyStatic" | [Runnable, MockMakers.mockito] + "SpyStatic" | [Runnable, CLOSURE] + "SpyStatic" | [Runnable, MockMakers.mockito] } } From f96bb0e95b144de170cfdb49c6150bd7746d5171 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 8 Nov 2025 20:05:17 +0100 Subject: [PATCH 2/6] Changed the implementation to now use new SpyStatic() API. --- .../mockito/MockitoMockMakerSettings.java | 5 +++ .../spockframework/runtime/SpecInternals.java | 13 ++---- .../java/spock/mock/IMockMakerSettings.java | 12 ++++++ .../src/main/java/spock/mock/MockingApi.java | 27 +----------- .../docs/extension/FancyMockMaker.java | 5 +++ .../mockito/MockitoStaticMocksSpec.groovy | 41 ++----------------- .../mock/MockingApiInvalidUsageSpec.groovy | 3 +- 7 files changed, 30 insertions(+), 76 deletions(-) diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/mockito/MockitoMockMakerSettings.java b/spock-core/src/main/java/org/spockframework/mock/runtime/mockito/MockitoMockMakerSettings.java index 19b6fccf22..df43198181 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/mockito/MockitoMockMakerSettings.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/mockito/MockitoMockMakerSettings.java @@ -42,6 +42,11 @@ public MockMakerId getMockMakerId() { return MockMakers.mockito.getMockMakerId(); } + @Override + public MockMakerId mockMakerId() { + return getMockMakerId(); + } + void applySettings(MockSettings mockitoSettings) { requireNonNull(mockitoSettings); Closure mockitoCode = uncheckedCast(this.mockitoCode.clone()); diff --git a/spock-core/src/main/java/org/spockframework/runtime/SpecInternals.java b/spock-core/src/main/java/org/spockframework/runtime/SpecInternals.java index 607e7c3fa6..82cd07d6ae 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/SpecInternals.java +++ b/spock-core/src/main/java/org/spockframework/runtime/SpecInternals.java @@ -333,18 +333,14 @@ private static T createMockImpl(Specification specification, String inferred } public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType) { - createStaticMockImpl(specification, MockNature.SPY, specifiedType, null, null); - } - - public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType, Closure closure) { - createStaticMockImpl(specification, MockNature.SPY, specifiedType, null, closure); + createStaticMockImpl(specification, MockNature.SPY, specifiedType, null); } public static void SpyStaticImpl(Specification specification, String inferredName, Type inferredType, Class specifiedType, IMockMakerSettings mockMakerSettings) { - createStaticMockImpl(specification, MockNature.SPY, specifiedType, mockMakerSettings, null); + createStaticMockImpl(specification, MockNature.SPY, specifiedType, mockMakerSettings); } - private static void createStaticMockImpl(Specification specification, MockNature nature, Class specifiedType, @Nullable IMockMakerSettings mockMakerSettings, @Nullable Closure closure) { + private static void createStaticMockImpl(Specification specification, MockNature nature, Class specifiedType, @Nullable IMockMakerSettings mockMakerSettings) { if (specifiedType == null) { throw new InvalidSpecException("The type must not be null."); } @@ -353,8 +349,5 @@ private static void createStaticMockImpl(Specification specification, MockNature options = Collections.singletonMap("mockMaker", mockMakerSettings); } createStaticMock(specification, specifiedType, nature, options); - if (closure != null) { - GroovyRuntimeUtil.invokeClosure(closure, specifiedType); - } } } diff --git a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java index cc588ce539..fcff2011d4 100644 --- a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java +++ b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java @@ -35,6 +35,11 @@ public MockMakerId getMockMakerId() { return id; } + @Override + public MockMakerId mockMakerId() { + return id; + } + @Override public String toString() { return id + " default mock maker settings"; @@ -46,4 +51,11 @@ public String toString() { * @return the {@link MockMakerId} to use, must not be {@code null}. */ MockMakerId getMockMakerId(); + + /** + *

Note: This method shall return the same as {@link #getMockMakerId()}. It is here to prevent Groovy to auto-convert Closures into {@link IMockMakerSettings} instances.

+ * + * @return the {@link MockMakerId} to use, must not be {@code null}. + */ + MockMakerId mockMakerId(); } diff --git a/spock-core/src/main/java/spock/mock/MockingApi.java b/spock-core/src/main/java/spock/mock/MockingApi.java index b9e0f0cb48..b4d081638c 100644 --- a/spock-core/src/main/java/spock/mock/MockingApi.java +++ b/spock-core/src/main/java/spock/mock/MockingApi.java @@ -1643,32 +1643,6 @@ public void SpyStatic(Class type) { throw invalidMockCreation(); } - /** - * Creates a thread-local spy for all static methods of the passed type. - *

- * Example: - * - *

-   *   SpyStatic(Person){
-   *      Person.staticMethod() >> "result"
-   *   }
-   * 
- * - *

Please use the class name as a prefix, instead of relying on {@code it} from Groovy.

- * - *

If you want to activate the static mocks on a different {@code Thread}, - * please call {@link #runWithThreadAwareMocks(Runnable)} on the different {@code Thread}. - * - * @param type the type of which the static methods shall be spied - * @param interactions a description of the static spy's interactions - */ - @Beta - public void SpyStatic(Class type, Closure interactions //Note: We can't specify here @ClosureParams or @DelegatesTo because there is no syntax to express that it means the Class object - ) { - - throw invalidMockCreation(); - } - /** * Creates a thread-local spy for all static methods of the passed type. *

@@ -1689,6 +1663,7 @@ public void SpyStatic(Class type, IMockMakerSettings mockMakerSettings) { throw invalidMockCreation(); } + /** * Runs the code with the thread-aware mocks activated on the current {@link Thread}. * diff --git a/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java b/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java index 0034b6478c..ec6056eb3f 100644 --- a/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java +++ b/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java @@ -98,6 +98,11 @@ public MockMakerId getMockMakerId() { return FancyMockMaker.ID; } + @Override + public MockMakerId mockMakerId() { + return getMockMakerId(); + } + public void withSerialization() { serialization = true; } diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index 62aa6bf0ea..a8972c6050 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -492,52 +492,17 @@ class MockitoStaticMocksSpec extends Specification { !StaticClass.staticVarargsMethod("test2") } - def "SpyStatic with closure shall not lead an exception"() { + def "SpyStatic with closure shall fail with MissingMethodException because it can not auto-convert the closure to IMockMakerSettings"() { when: SpyStatic(StaticClass) { } then: - noExceptionThrown() + def ex = thrown(MissingMethodException) + ex.message.contains("No signature of method: static org.spockframework.runtime.SpecInternals.SpyStaticImpl() is applicable for argument types") } - def "SpyStatic with closure can specify interactions"() { - given: - SpyStatic(StaticClass) { - StaticClass.staticVarargsMethod("test") >> true - } - - expect: - StaticClass.staticVarargsMethod("test") - } - - def "SpyStatic with closure could use it instead of ClassName"() { - given: - SpyStatic(StaticClass) { - //Note: This does not have code completion support. - it.staticVarargsMethod("test") >> true - } - - expect: - StaticClass.staticVarargsMethod("test") - } - - def "SpyStatic with closure could use no prefix instead of ClassName"() { - given: - SpyStatic(StaticClass) { - //Note: This does not have code completion support. - staticVarargsMethod("test") >> true - } - - expect: - StaticClass.staticVarargsMethod("test") - } - - /** - * The IMockMakerSettings has only a single method, so it could be converted from a Groovy closure automatically. - * This shall check that the API produces a nice error to the user in such cases. - */ def "SpyStatic with closure casted as IMockMakerSettings shall produce nice error message"() { when: SpyStatic(StaticClass, { diff --git a/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy b/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy index 23b6e50b96..f5676a8d3f 100644 --- a/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy +++ b/spock-specs/src/test/groovy/spock/mock/MockingApiInvalidUsageSpec.groovy @@ -95,7 +95,6 @@ class MockingApiInvalidUsageSpec extends Specification { "GroovySpy" | [CLOSURE] "SpyStatic" | [Runnable] - "SpyStatic" | [Runnable, CLOSURE] - "SpyStatic" | [Runnable, MockMakers.mockito] + "SpyStatic" | [Runnable, MockMakers.mockito] } } From 6d77ab079a136da7b0765a4e8c7037a683ea1c72 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 8 Nov 2025 20:30:49 +0100 Subject: [PATCH 3/6] Fixup test for Groovy 5 --- .../mock/runtime/mockito/MockitoStaticMocksSpec.groovy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index a8972c6050..a755630a67 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -22,6 +22,8 @@ import org.mockito.exceptions.base.MockitoException import org.spockframework.mock.CannotCreateMockException import org.spockframework.mock.MockUtil import org.spockframework.runtime.InvalidSpecException +import org.spockframework.runtime.SpecInternals +import org.spockframework.runtime.model.SpecInfo import spock.lang.Issue import spock.lang.Shared import spock.lang.Specification @@ -500,7 +502,13 @@ class MockitoStaticMocksSpec extends Specification { then: def ex = thrown(MissingMethodException) - ex.message.contains("No signature of method: static org.spockframework.runtime.SpecInternals.SpyStaticImpl() is applicable for argument types") + ex.type == SpecInternals + ex.method == "SpyStaticImpl" + ex.arguments[0] == this + ex.arguments[1] == null + ex.arguments[2] == null + ex.arguments[3] == StaticClass + ex.arguments[4] instanceof Closure } def "SpyStatic with closure casted as IMockMakerSettings shall produce nice error message"() { From 9751a3ed8743b24782962494f8bca09455b63591 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 8 Nov 2025 21:07:47 +0100 Subject: [PATCH 4/6] Revert the addition of the method to IMockMakerSettings because it would break API. --- .../mock/runtime/MockMakerRegistry.java | 28 +++++++++++++++---- .../mockito/MockitoMockMakerSettings.java | 5 ---- .../java/spock/mock/IMockMakerSettings.java | 12 -------- .../docs/extension/FancyMockMaker.java | 5 ---- .../mockito/MockitoStaticMocksSpec.groovy | 26 ++++++++--------- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java b/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java index 7e49aa77e4..dc9360bb9a 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/MockMakerRegistry.java @@ -80,7 +80,7 @@ public static MockMakerRegistry createFromServiceLoader(MockMakerConfiguration c MockMakerId preferredMockMakerId; if (preferredMockMakerParam != null) { preferredMockMakerId = preferredMockMakerParam.getMockMakerId(); - if (makerMap.get(preferredMockMakerParam.getMockMakerId()) == null) { + if (makerMap.get(preferredMockMakerId) == null) { throw new IllegalStateException("No IMockMaker with ID '" + preferredMockMakerId + "' exists, but was request via mockMaker.preferredMockMaker configuration. Is a runtime dependency missing?"); } } else { @@ -145,13 +145,10 @@ public IStaticMock makeStaticMock(IMockCreationSettings settings) throws CannotC public T makeMockInternal(IMockCreationSettings settings, BiFunction code) throws CannotCreateMockException { IMockMakerSettings mockMakerSettings = settings.getMockMakerSettings(); if (mockMakerSettings != null) { - MockMakerId mockMakerId = mockMakerSettings.getMockMakerId(); + MockMakerId mockMakerId = getMockMakerId(settings, mockMakerSettings); IMockMaker mockMaker = makerMap.get(mockMakerId); if (mockMaker == null) { - if (mockMakerId == null && (mockMakerSettings instanceof Proxy || mockMakerSettings instanceof Closure)) { - throw new CannotCreateMockException(settings.getMockType(), " because the MockMakerSettings returned the invalid ID 'null'. Please check that a closure did not get accidentally casted to IMockMakerSettings." - + " The settings object was '" + GroovyRuntimeUtil.toString(mockMakerSettings) + "'."); - } + checkForStaticMockUsageWithClosure(settings, mockMakerSettings, mockMakerId, null); throw new CannotCreateMockException(settings.getMockType(), " because MockMaker with ID '" + mockMakerId + "' does not exist."); } verifyIsMockable(mockMaker, settings); @@ -160,6 +157,25 @@ public T makeMockInternal(IMockCreationSettings settings, BiFunction mockitoCode = uncheckedCast(this.mockitoCode.clone()); diff --git a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java index fcff2011d4..cc588ce539 100644 --- a/spock-core/src/main/java/spock/mock/IMockMakerSettings.java +++ b/spock-core/src/main/java/spock/mock/IMockMakerSettings.java @@ -35,11 +35,6 @@ public MockMakerId getMockMakerId() { return id; } - @Override - public MockMakerId mockMakerId() { - return id; - } - @Override public String toString() { return id + " default mock maker settings"; @@ -51,11 +46,4 @@ public String toString() { * @return the {@link MockMakerId} to use, must not be {@code null}. */ MockMakerId getMockMakerId(); - - /** - *

Note: This method shall return the same as {@link #getMockMakerId()}. It is here to prevent Groovy to auto-convert Closures into {@link IMockMakerSettings} instances.

- * - * @return the {@link MockMakerId} to use, must not be {@code null}. - */ - MockMakerId mockMakerId(); } diff --git a/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java b/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java index ec6056eb3f..0034b6478c 100644 --- a/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java +++ b/spock-specs/src/test/groovy/org/spockframework/docs/extension/FancyMockMaker.java @@ -98,11 +98,6 @@ public MockMakerId getMockMakerId() { return FancyMockMaker.ID; } - @Override - public MockMakerId mockMakerId() { - return getMockMakerId(); - } - public void withSerialization() { serialization = true; } diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index a755630a67..37bf559552 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -494,33 +494,31 @@ class MockitoStaticMocksSpec extends Specification { !StaticClass.staticVarargsMethod("test2") } - def "SpyStatic with closure shall fail with MissingMethodException because it can not auto-convert the closure to IMockMakerSettings"() { + def "SpyStatic with closure as IMockMakerSettings shall produce nice error message"() { when: SpyStatic(StaticClass) { } then: - def ex = thrown(MissingMethodException) - ex.type == SpecInternals - ex.method == "SpyStaticImpl" - ex.arguments[0] == this - ex.arguments[1] == null - ex.arguments[2] == null - ex.arguments[3] == StaticClass - ex.arguments[4] instanceof Closure + def ex = thrown(CannotCreateMockException) + ex.message.startsWith( + """Cannot create mock for class $StaticClass.name because the MockMakerSettings returned the invalid ID 'null'. +The syntax SpyStatic(StaticClass){} is not supported, please use SpyStatic(StaticClass) without a Closure instead.""") } - def "SpyStatic with closure casted as IMockMakerSettings shall produce nice error message"() { + def "SpyStatic with closure returning something shall produce nice error message"() { when: - SpyStatic(StaticClass, { - - } as IMockMakerSettings) + SpyStatic(StaticClass) { + "Dummy" + } then: def ex = thrown(CannotCreateMockException) ex.message.startsWith( - "Cannot create mock for class $StaticClass.name because the MockMakerSettings returned the invalid ID 'null'. Please check that a closure did not get accidentally casted to IMockMakerSettings. The settings object was ") + """Cannot create mock for class $StaticClass.name because the MockMakerSettings returned the invalid ID 'null'. +The syntax SpyStatic(StaticClass){} is not supported, please use SpyStatic(StaticClass) without a Closure instead.""") + ex.cause instanceof ClassCastException } static class StaticClass { From e1404609a71baf4794ae9ff28453075f16732a38 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 8 Nov 2025 21:21:23 +0100 Subject: [PATCH 5/6] Added test for non-static exception. --- .../mock/runtime/mockito/MockitoStaticMocksSpec.groovy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index 37bf559552..c55305cb5f 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -521,6 +521,16 @@ The syntax SpyStatic(StaticClass){} is not supported, please use SpyStatic(Stati ex.cause instanceof ClassCastException } + def "ClassCastException inside the getMockMakerId() from a non-static Spy is thrown as-is and is not processed by the SpyStatic(){} closure check"() { + when: + Spy(Runnable, mockMaker: { + throw new ClassCastException() + }) + + then: + thrown(ClassCastException) + } + static class StaticClass { String instanceMethod() { From f3747e6eb54f1cd28242ffaa9048c00b3798b0b0 Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Sat, 8 Nov 2025 21:32:50 +0100 Subject: [PATCH 6/6] Added test for non-static exception. --- .../mock/runtime/mockito/MockitoStaticMocksSpec.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy index c55305cb5f..fede423b3b 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoStaticMocksSpec.groovy @@ -525,8 +525,8 @@ The syntax SpyStatic(StaticClass){} is not supported, please use SpyStatic(Stati when: Spy(Runnable, mockMaker: { throw new ClassCastException() - }) - + } as IMockMakerSettings) + then: thrown(ClassCastException) }