diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc index 3241d6b7bc..1b7eac9ef0 100644 --- a/docs/release_notes.adoc +++ b/docs/release_notes.adoc @@ -24,6 +24,7 @@ include::include.adoc[] === Breaking Changes +* Declaring an interaction on a `null` target now throws `InvalidSpecException` instead of being silently ignored (or failing with an unrelated NPE for some mock makers) spockIssue:2339[] * Mock/Stub checks on `Comparable` with `T` being something other than `Object` now compare using the java identity hash code instead of always being equal spockIssue:2352[] == 2.4 (2025-12-11) diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/TargetConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/TargetConstraint.java index b6e3753ac4..ee19e6b834 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/TargetConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/TargetConstraint.java @@ -31,6 +31,9 @@ public class TargetConstraint implements IInvocationConstraint, IInteractionAwar private IMockInteraction interaction; public TargetConstraint(Object target) { + if (target == null) { + throw new InvalidSpecException("Interaction on a null object is invalid"); + } this.target = target; } diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoMockMakerSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoMockMakerSpec.groovy index 006b32968c..ee92afeeea 100644 --- a/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoMockMakerSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/mock/runtime/mockito/MockitoMockMakerSpec.groovy @@ -32,6 +32,7 @@ import org.spockframework.mock.CannotCreateMockException import org.spockframework.mock.MockUtil import org.spockframework.mock.runtime.ByteBuddyTestClassLoader import org.spockframework.runtime.GroovyRuntimeUtil +import org.spockframework.runtime.InvalidSpecException import static spock.mock.MockMakers.mockito @@ -516,17 +517,19 @@ Can not mock final classes with the following settings : mockUtil.isMock(m) } - @Issue("https://github.com/spockframework/spock/issues/2337") - def "NPE when stubbing method on null object"() { + @Issue(["https://github.com/spockframework/spock/issues/2337", "https://github.com/spockframework/spock/issues/2339"]) + def "stubbing method on null object does not NPE and reports InvalidSpecException"() { given: Object nullObj = null when: - //Issue #2337: MockitoMockMaker passes null to Mockito's getHandler() throws NPE + // #2337: MockitoMockMaker must not pass null to Mockito's getHandler() + // #2339: null target interactions are rejected with a clear error nullObj.toString() >> "" then: - noExceptionThrown() + def ex = thrown(InvalidSpecException) + ex.message == "Interaction on a null object is invalid" } } diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/mock/NullTargetInteraction.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/mock/NullTargetInteraction.groovy new file mode 100644 index 0000000000..8a4cfe5cff --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/mock/NullTargetInteraction.groovy @@ -0,0 +1,60 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.spockframework.smoke.mock + +import org.spockframework.runtime.InvalidSpecException +import spock.lang.Issue +import spock.lang.Specification + +@Issue("https://github.com/spockframework/spock/issues/2339") +class NullTargetInteraction extends Specification { + def "interaction on null target throws InvalidSpecException"() { + given: + Object nullObj = null + + when: + nullObj.toString() >> "" + + then: + def ex = thrown(InvalidSpecException) + ex.message == "Interaction on a null object is invalid" + } + + def "required interaction on null target throws InvalidSpecException"() { + given: + List nullList = null + + when: + 1 * nullList.size() + + then: + def ex = thrown(InvalidSpecException) + ex.message == "Interaction on a null object is invalid" + } + + def "interaction on real mock is still accepted"() { + given: + List list = Mock() + + when: + list.size() >> 0 + + then: + notThrown(InvalidSpecException) + list.size() == 0 + } +}