From 250e4dfc1fb47787a9b810f472204df644b19beb Mon Sep 17 00:00:00 2001 From: AndreasTu Date: Mon, 14 Aug 2023 18:03:57 +0200 Subject: [PATCH] Mocking in given is ignored when mocked in setup The Interactions now allow to override other interactions, if these are the same and do not specify a cardinality. This fixes #962 --- docs/interaction_based_testing.adoc | 32 ++ .../mock/DefaultInteraction.java | 17 + .../mock/IArgumentConstraint.java | 5 + .../mock/IInvocationConstraint.java | 5 + .../spockframework/mock/IMockInteraction.java | 21 + .../constraint/CodeArgumentConstraint.java | 15 +- .../constraint/EqualArgumentConstraint.java | 9 + .../constraint/EqualMethodNameConstraint.java | 11 + .../EqualPropertyNameConstraint.java | 12 + .../NamedArgumentListConstraint.java | 11 + .../NegatingArgumentConstraint.java | 9 + .../PositionalArgumentListConstraint.java | 9 + .../constraint/RegexMethodNameConstraint.java | 10 + .../RegexPropertyNameConstraint.java | 10 + .../SpreadWildcardArgumentConstraint.java | 5 + .../mock/constraint/TargetConstraint.java | 9 + .../constraint/TypeArgumentConstraint.java | 12 + .../WildcardArgumentConstraint.java | 5 + .../WildcardMethodNameConstraint.java | 5 + .../mock/runtime/InteractionBuilder.java | 14 +- .../mock/runtime/InteractionScope.java | 20 +- .../mock/runtime/MockInteraction.java | 30 +- .../runtime/MockInteractionDecorator.java | 15 + .../spockframework/util/CollectionUtil.java | 15 + .../interaction/InteractionDocSpec.groovy | 25 + .../mock/DefaultInteractionSpec.groovy | 42 ++ .../JavaMockInteractionOverridesSpec.groovy | 519 ++++++++++++++++++ .../util/CollectionUtilSpec.groovy | 21 + 28 files changed, 904 insertions(+), 9 deletions(-) create mode 100644 spock-specs/src/test/groovy/org/spockframework/mock/DefaultInteractionSpec.groovy create mode 100644 spock-specs/src/test/groovy/org/spockframework/smoke/mock/JavaMockInteractionOverridesSpec.groovy diff --git a/docs/interaction_based_testing.adoc b/docs/interaction_based_testing.adoc index 98ce0a21af..34945b7921 100644 --- a/docs/interaction_based_testing.adoc +++ b/docs/interaction_based_testing.adoc @@ -155,6 +155,38 @@ _ * subscriber.receive("hello") // any number of calls, including zero // (rarely needed; see 'Strict Mocking') ---- +==== Interactions with Cardinality + +Interactions with cardinality are additive, this means if you specify two interactions with cardinality (with same target, method and argument) +they are processed after each other: + +.Interactions with Cardinality are additive +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/InteractionDocSpec.groovy[tag=two-interactions-cardinality] +---- + +==== Interactions without Cardinality + +Interactions without cardinality are **not** additive. +If you specify multiple interactions without cardinality (with same target, method and argument) +only the last interaction is respected. + +That allows you to specify default interactions in the `setup` method, but override it in +some of the `feature` methods. + + +.Interactions without Cardinality are **not** additive +[source,groovy,indent=0] +---- +include::{sourcedir}/interaction/InteractionDocSpec.groovy[tag=two-interactions-without-cardinality] +---- + +NOTE: An interaction without cardinality will **not** override an interaction with cardinality and vice versa. + +CAUTION: Interactions in `then` blocks have always precedence over any other interactions regardless of their cardinality. + So an interaction in a `then` block will apply regardless what was specified in `setup`, `given` or the `when`. + === Target Constraint The target constraint of an interaction describes which mock object is expected to receive the method call: diff --git a/spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java b/spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java index 24899b7000..342a570029 100644 --- a/spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java +++ b/spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java @@ -15,9 +15,26 @@ import org.spockframework.util.UnreachableCodeError; +import java.util.Collections; import java.util.List; abstract class DefaultInteraction implements IMockInteraction { + + @Override + public boolean isCardinalitySpecified() { + return false; + } + + @Override + public List getConstraints() { + return Collections.emptyList(); + } + + @Override + public boolean isThisInteractionOverridableBy(IMockInteraction toCheck) { + return false; + } + @Override public int getLine() { return -1; diff --git a/spock-core/src/main/java/org/spockframework/mock/IArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/IArgumentConstraint.java index ea7664326f..564270dcae 100644 --- a/spock-core/src/main/java/org/spockframework/mock/IArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/IArgumentConstraint.java @@ -22,6 +22,11 @@ * @author Peter Niederwieser */ public interface IArgumentConstraint { + /** + * @param other the other element + * @return {@code true}, if both elements represent the same {@link IArgumentConstraint} from the declaration point of view + */ + boolean isDeclarationEqualTo(IArgumentConstraint other); boolean isSatisfiedBy(Object arg); String describeMismatch(Object arg); } diff --git a/spock-core/src/main/java/org/spockframework/mock/IInvocationConstraint.java b/spock-core/src/main/java/org/spockframework/mock/IInvocationConstraint.java index b5bd69900d..fe7081d214 100644 --- a/spock-core/src/main/java/org/spockframework/mock/IInvocationConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/IInvocationConstraint.java @@ -21,6 +21,11 @@ * @author Peter Niederwieser */ public interface IInvocationConstraint { + /** + * @param other the other element + * @return {@code true}, if both elements represent the same {@link IInvocationConstraint} from the declaration point of view + */ + boolean isDeclarationEqualTo(IInvocationConstraint other); boolean isSatisfiedBy(IMockInvocation invocation); String describeMismatch(IMockInvocation invocation); } diff --git a/spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java b/spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java index 103a8e0315..17febf358a 100644 --- a/spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java +++ b/spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java @@ -32,6 +32,27 @@ public interface IMockInteraction { String getText(); + /** + * @return {@code true}, if the user had specified cardinality + */ + boolean isCardinalitySpecified(); + + List getConstraints(); + + /** + * Returns {@code true}, if this interaction, can be overridden by the {@link IMockInteraction} {@code toCheck}. + * + *

This is possible if: + *

    + *
  • Both have no specified cardinality
  • + *
  • Both have the same {@link IInvocationConstraint}s, see {@link IInvocationConstraint#isDeclarationEqualTo(IInvocationConstraint)}
  • + *
+ * + * @param toCheck the element, which wants to override this. + * @return {@code true}, if this can be overridden. + */ + boolean isThisInteractionOverridableBy(IMockInteraction toCheck); + boolean matches(IMockInvocation invocation); @Nullable diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/CodeArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/CodeArgumentConstraint.java index 539c5bffb9..c25a4737ff 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/CodeArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/CodeArgumentConstraint.java @@ -21,17 +21,28 @@ import groovy.lang.Closure; +import java.util.Objects; + /** * * @author Peter Niederwieser */ public class CodeArgumentConstraint implements IArgumentConstraint { - private final Closure code; + private final Closure code; - public CodeArgumentConstraint(Closure code) { + public CodeArgumentConstraint(Closure code) { this.code = code; } + @Override + public boolean isDeclarationEqualTo(IArgumentConstraint other) { + if (other instanceof CodeArgumentConstraint) { + CodeArgumentConstraint o = (CodeArgumentConstraint) other; + return Objects.equals(this.code, o.code); + } + return false; + } + @Override public boolean isSatisfiedBy(Object argument) { try { diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualArgumentConstraint.java index 3760b5199e..c92e36d5ec 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualArgumentConstraint.java @@ -31,6 +31,15 @@ public EqualArgumentConstraint(Object expected) { this.expected = expected; } + @Override + public boolean isDeclarationEqualTo(IArgumentConstraint other) { + if (other instanceof EqualArgumentConstraint) { + EqualArgumentConstraint o = (EqualArgumentConstraint) other; + return this.expected == o.expected; + } + return false; + } + @Override public boolean isSatisfiedBy(Object arg) { if (HamcrestFacade.isMatcher(expected)) { diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualMethodNameConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualMethodNameConstraint.java index 020120e600..729cd630d7 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualMethodNameConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualMethodNameConstraint.java @@ -20,6 +20,8 @@ import org.spockframework.runtime.Condition; import org.spockframework.util.CollectionUtil; +import java.util.Objects; + /** * * @author Peter Niederwieser @@ -31,6 +33,15 @@ public EqualMethodNameConstraint(String methodName) { this.methodName = methodName; } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof EqualMethodNameConstraint) { + EqualMethodNameConstraint o = (EqualMethodNameConstraint) other; + return Objects.equals(this.methodName, o.methodName); + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { return invocation.getMethod().getName().equals(methodName); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualPropertyNameConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualPropertyNameConstraint.java index 737916baef..880fe29140 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/EqualPropertyNameConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/EqualPropertyNameConstraint.java @@ -14,10 +14,13 @@ package org.spockframework.mock.constraint; +import org.spockframework.mock.IInvocationConstraint; import org.spockframework.mock.IMockInvocation; import org.spockframework.runtime.Condition; import org.spockframework.util.CollectionUtil; +import java.util.Objects; + /** * * @author Peter Niederwieser @@ -29,6 +32,15 @@ public EqualPropertyNameConstraint(String propertyName) { this.propertyName = propertyName; } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof EqualPropertyNameConstraint) { + EqualPropertyNameConstraint o = (EqualPropertyNameConstraint) other; + return Objects.equals(this.propertyName, o.propertyName); + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { return propertyName.equals(getPropertyName(invocation)); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/NamedArgumentListConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/NamedArgumentListConstraint.java index 5853ead0fc..6fce72ae0d 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/NamedArgumentListConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/NamedArgumentListConstraint.java @@ -38,6 +38,17 @@ public NamedArgumentListConstraint(List argNames, List argConstraints } } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof PositionalArgumentListConstraint) { + PositionalArgumentListConstraint o = (PositionalArgumentListConstraint) other; + return CollectionUtil.areListsEqual(this.argConstraints, o.argConstraints, IArgumentConstraint::isDeclarationEqualTo); + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { List args = invocation.getArguments(); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/RegexMethodNameConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/RegexMethodNameConstraint.java index d18a75fb02..ac2308331d 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/RegexMethodNameConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/RegexMethodNameConstraint.java @@ -20,6 +20,7 @@ import org.spockframework.runtime.Condition; import org.spockframework.util.CollectionUtil; +import java.util.Objects; import java.util.regex.Pattern; /** @@ -33,6 +34,15 @@ public RegexMethodNameConstraint(String regex) { pattern = Pattern.compile(regex); } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof RegexMethodNameConstraint) { + RegexMethodNameConstraint o = (RegexMethodNameConstraint) other; + return Objects.equals(this.pattern.toString(), o.pattern.toString()); + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { return pattern.matcher(invocation.getMethod().getName()).matches(); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/RegexPropertyNameConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/RegexPropertyNameConstraint.java index c07c093160..6dd252bfe1 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/RegexPropertyNameConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/RegexPropertyNameConstraint.java @@ -18,6 +18,7 @@ import org.spockframework.runtime.*; import org.spockframework.util.CollectionUtil; +import java.util.Objects; import java.util.regex.Pattern; /** @@ -31,6 +32,15 @@ public RegexPropertyNameConstraint(String regex) { pattern = Pattern.compile(regex); } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof RegexPropertyNameConstraint) { + RegexPropertyNameConstraint o = (RegexPropertyNameConstraint) other; + return Objects.equals(this.pattern.toString(), o.pattern.toString()); + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { String propertyName = getPropertyName(invocation); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/SpreadWildcardArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/SpreadWildcardArgumentConstraint.java index 8fe132d62f..6116121c03 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/SpreadWildcardArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/SpreadWildcardArgumentConstraint.java @@ -28,6 +28,11 @@ public class SpreadWildcardArgumentConstraint implements IArgumentConstraint { private SpreadWildcardArgumentConstraint() {} + @Override + public boolean isDeclarationEqualTo(IArgumentConstraint other) { + return this == other; + } + @Override public boolean isSatisfiedBy(Object arg) { throw new InvalidSpecException("*_ may only appear at the end of an argument list"); 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 5df131f027..ad70d0e6bf 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 @@ -33,6 +33,15 @@ public TargetConstraint(Object target) { this.target = target; } + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + if (other instanceof TargetConstraint) { + TargetConstraint o = (TargetConstraint) other; + return this.target == o.target; + } + return false; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { return invocation.getMockObject().matches(target, interaction); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/TypeArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/TypeArgumentConstraint.java index 1684c839f4..b01593eb22 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/TypeArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/TypeArgumentConstraint.java @@ -20,6 +20,8 @@ import org.spockframework.runtime.Condition; import org.spockframework.util.CollectionUtil; +import java.util.Objects; + /** * * @author Peter Niederwieser @@ -33,6 +35,16 @@ public TypeArgumentConstraint(Class type, IArgumentConstraint constraint) { this.constraint = constraint; } + @Override + public boolean isDeclarationEqualTo(IArgumentConstraint other) { + if (other instanceof TypeArgumentConstraint) { + TypeArgumentConstraint o = (TypeArgumentConstraint) other; + return Objects.equals(this.type, o.type) && + this.constraint.isDeclarationEqualTo(o.constraint); + } + return false; + } + @Override public boolean isSatisfiedBy(Object argument) { return type.isInstance(argument) && constraint.isSatisfiedBy(argument); diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardArgumentConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardArgumentConstraint.java index 2150c3ddb1..5d691d9985 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardArgumentConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardArgumentConstraint.java @@ -27,6 +27,11 @@ public class WildcardArgumentConstraint implements IArgumentConstraint { private WildcardArgumentConstraint() {} + @Override + public boolean isDeclarationEqualTo(IArgumentConstraint other) { + return this == other; + } + @Override public boolean isSatisfiedBy(Object arg) { return true; diff --git a/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardMethodNameConstraint.java b/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardMethodNameConstraint.java index d4636695b5..86d3049ad2 100644 --- a/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardMethodNameConstraint.java +++ b/spock-core/src/main/java/org/spockframework/mock/constraint/WildcardMethodNameConstraint.java @@ -24,6 +24,11 @@ public class WildcardMethodNameConstraint implements IInvocationConstraint { private WildcardMethodNameConstraint() {} + @Override + public boolean isDeclarationEqualTo(IInvocationConstraint other) { + return this == other; + } + @Override public boolean isSatisfiedBy(IMockInvocation invocation) { return DefaultJavaLangObjectInteractions.INSTANCE.match(invocation) == null; diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionBuilder.java b/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionBuilder.java index aceec7c416..044f4d74f4 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionBuilder.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionBuilder.java @@ -34,8 +34,11 @@ public class InteractionBuilder { private final int line; private final int column; private final String text; - - private int minCount = 0; + /** + * Constant to mark if the user has specified a count/cardinality + */ + private static final int COUNT_NOT_SPECIFIED = Integer.MIN_VALUE; + private int minCount = COUNT_NOT_SPECIFIED; private int maxCount = Integer.MAX_VALUE; private List invConstraints = new ArrayList<>(); private List argNames; @@ -183,7 +186,12 @@ public InteractionBuilder addIterableResponse(Object iterable) { public static final String BUILD = "build"; public IMockInteraction build() { - return new MockInteraction(line, column, text, minCount, maxCount, invConstraints, + boolean hasCardinality = true; + if (minCount == COUNT_NOT_SPECIFIED) { + hasCardinality = false; + minCount = 0; + } + return new MockInteraction(line, column, text, hasCardinality, minCount, maxCount, invConstraints, responseGeneratorChain.isEmpty() ? new DefaultResponseGenerator() : responseGeneratorChain); } diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java b/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java index 5332a0fca4..5b7d3248bc 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java @@ -16,6 +16,7 @@ package org.spockframework.mock.runtime; +import org.jetbrains.annotations.NotNull; import org.spockframework.mock.*; import org.spockframework.util.ExceptionUtil; @@ -36,7 +37,22 @@ public class InteractionScope implements IInteractionScope { @Override public void addInteraction(final IMockInteraction interaction) { - interactions.add(new MockInteractionDecorator(interaction) { + MockInteractionDecorator decorator = createMockInteractionDecorator(interaction); + ListIterator it = interactions.listIterator(); + while (it.hasNext()) { + IMockInteraction next = it.next(); + if (next.isThisInteractionOverridableBy(decorator)) { + it.set(decorator); + return; + } + } + + interactions.add(decorator); + } + + @NotNull + private MockInteractionDecorator createMockInteractionDecorator(IMockInteraction interaction) { + return new MockInteractionDecorator(interaction) { final int myRegistrationZone = currentRegistrationZone; @Override @@ -66,7 +82,7 @@ public Object accept(IMockInvocation invocation) { public String describeMismatch(IMockInvocation invocation) { return interaction.describeMismatch(invocation); } - }); + }; } @Override diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java b/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java index 69e917a2c2..d54049b56c 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java @@ -17,6 +17,7 @@ package org.spockframework.mock.runtime; import org.spockframework.mock.*; +import org.spockframework.util.CollectionUtil; import org.spockframework.util.TextUtil; import java.util.*; @@ -32,17 +33,24 @@ public class MockInteraction implements IMockInteraction { private final String text; private final int minCount; private final int maxCount; + private final boolean cardinalitySpecified; private final List constraints; private final IResponseGenerator responseGenerator; private final List acceptedInvocations = new ArrayList<>(); - public MockInteraction(int line, int column, String text, int minCount, - int maxCount, List constraints, + public MockInteraction(int line, + int column, + String text, + boolean cardinalitySpecified, + int minCount, + int maxCount, + List constraints, IResponseGenerator responseGenerator) { this.line = line; this.column = column; this.text = text; + this.cardinalitySpecified = cardinalitySpecified; this.minCount = minCount; this.maxCount = maxCount; this.constraints = constraints; @@ -55,6 +63,24 @@ public MockInteraction(int line, int column, String text, int minCount, } } + @Override + public boolean isCardinalitySpecified() { + return cardinalitySpecified; + } + + @Override + public List getConstraints() { + return Collections.unmodifiableList(constraints); + } + + @Override + public boolean isThisInteractionOverridableBy(IMockInteraction toCheck) { + if (this.isCardinalitySpecified() || toCheck.isCardinalitySpecified()) { + return false; + } + return CollectionUtil.areListsEqual(this.constraints, toCheck.getConstraints(), IInvocationConstraint::isDeclarationEqualTo); + } + @Override public boolean matches(IMockInvocation invocation) { for (IInvocationConstraint constraint : constraints) { diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java b/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java index 4ef7eb8c05..3791df91e7 100644 --- a/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java +++ b/spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java @@ -40,6 +40,21 @@ public String getText() { return decorated.getText(); } + @Override + public boolean isCardinalitySpecified() { + return decorated.isCardinalitySpecified(); + } + + @Override + public List getConstraints() { + return decorated.getConstraints(); + } + + @Override + public boolean isThisInteractionOverridableBy(IMockInteraction toCheck) { + return decorated.isThisInteractionOverridableBy(toCheck); + } + @Override public boolean matches(IMockInvocation invocation) { return decorated.matches(invocation); diff --git a/spock-core/src/main/java/org/spockframework/util/CollectionUtil.java b/spock-core/src/main/java/org/spockframework/util/CollectionUtil.java index 473a3ab1a0..d50a0fea11 100644 --- a/spock-core/src/main/java/org/spockframework/util/CollectionUtil.java +++ b/spock-core/src/main/java/org/spockframework/util/CollectionUtil.java @@ -16,6 +16,7 @@ import java.lang.reflect.Array; import java.util.*; +import java.util.function.BiPredicate; import static java.util.Arrays.asList; @@ -209,4 +210,18 @@ public static int findIndexOf(Iterable iterable, IFunction boolean areListsEqual(List l1, List l2, BiPredicate equalFunction) { + if (l1.size() != l2.size()) { + return false; + } + for (int i = 0; i < l1.size(); i++) { + T e1 = l1.get(i); + T e2 = l2.get(i); + if (!equalFunction.test(e1, e2)) { + return false; + } + } + return true; + } } diff --git a/spock-specs/src/test/groovy/org/spockframework/docs/interaction/InteractionDocSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/docs/interaction/InteractionDocSpec.groovy index d620cf60d5..576fa02cca 100644 --- a/spock-specs/src/test/groovy/org/spockframework/docs/interaction/InteractionDocSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/docs/interaction/InteractionDocSpec.groovy @@ -3,6 +3,7 @@ package org.spockframework.docs.interaction import org.spockframework.mock.TooFewInvocationsError import spock.lang.* +import java.util.function.Supplier import java.util.regex.Pattern import static org.hamcrest.CoreMatchers.endsWith @@ -54,6 +55,30 @@ class InteractionDocSpec extends Specification { then: 2 * _.receive(_) } + + def "Mock two interactions with cardinality"() { + // tag::two-interactions-cardinality[] + given: + Supplier supplier = Mock() + 1 * supplier.get() >> "First" + 1 * supplier.get() >> "Second" + expect: + supplier.get() == "First" + supplier.get() == "Second" + // end::two-interactions-cardinality[] + } + + def "Mock two interactions without cardinality"() { + // tag::two-interactions-without-cardinality[] + given: + Supplier supplier = Mock() + supplier.get() >> "First" + supplier.get() >> "Second" + expect: + supplier.get() == "Second" + supplier.get() == "Second" + // end::two-interactions-without-cardinality[] + } } class SubscriberImpl implements Subscriber { diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/DefaultInteractionSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/DefaultInteractionSpec.groovy new file mode 100644 index 0000000000..85603db8d1 --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/mock/DefaultInteractionSpec.groovy @@ -0,0 +1,42 @@ +package org.spockframework.mock + +import org.spockframework.util.UnreachableCodeError +import spock.lang.Specification + +class DefaultInteractionSpec extends Specification { + + def i = Spy(DefaultInteraction) + + def "default implementation"() { + expect: + !i.isCardinalitySpecified() + i.constraints.isEmpty() + !i.isThisInteractionOverridableBy(i) + i.line == -1 + i.column == -1 + i.satisfied + !i.exhausted + !i.required + } + + def "getAcceptedInvocations"() { + when: + i.getAcceptedInvocations() + then: + thrown(UnreachableCodeError) + } + + def "computeSimilarityScore"() { + when: + i.computeSimilarityScore(null) + then: + thrown(UnreachableCodeError) + } + + def "describeMismatch"() { + when: + i.describeMismatch(null) + then: + thrown(UnreachableCodeError) + } +} diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/mock/JavaMockInteractionOverridesSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/mock/JavaMockInteractionOverridesSpec.groovy new file mode 100644 index 0000000000..f7a2f26add --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/mock/JavaMockInteractionOverridesSpec.groovy @@ -0,0 +1,519 @@ +package org.spockframework.smoke.mock + +import org.spockframework.EmbeddedSpecification +import org.spockframework.mock.TooFewInvocationsError +import spock.lang.FailsWith +import spock.lang.Issue +import spock.lang.PendingFeature + +class JavaMockInteractionOverridesSpec extends EmbeddedSpecification { + def ifMock = Mock(InterfaceType) + + def setup() { + ifMock.methodFromSetup() >> 1 + } + + def "given block two same interactions after each other, the second shall win due to no cardinality"() { + given: + ifMock.method() >> 1 + //Second behavior shall win, because the first did no specify cardinality + ifMock.method() >> 2 + when: + def result = ifMock.method() + then: "Only the second behavior shall apply, because nothing had cardinality" + result == 2 + when: + result = ifMock.method() + then: + result == 2 + } + + def "when block two same interactions after each other, the second shall win due to no cardinality"() { + when: + ifMock.method() >> 1 + //Second behavior shall win, because the first did no specify cardinality + ifMock.method() >> 2 + def result = ifMock.method() + then: "Only the second behavior shall apply, because nothing had cardinality" + result == 2 + when: + result = ifMock.method() + then: + result == 2 + } + + def "Second when block overrides first when block"() { + when: + ifMock.method() >> 1 + def result = ifMock.method() + then: + result == 1 + when: + ifMock.method() >> 2 + result = ifMock.method() + then: + result == 2 + } + + def "then block two same interactions after each other, the second shall win due to no cardinality"() { + when: + def result1 = ifMock.method() + def result2 = ifMock.method() + then: "Only the second behavior shall apply, because nothing had cardinality" + ifMock.method() >> 1 + //Second behavior shall win, because the first did not specify any cardinality + ifMock.method() >> 2 + result1 == 2 + result2 == 2 + } + + def "two interactions after each other, but first has any cardinality, the second shall not win"() { + given: + _ * ifMock.method() >> 1 + //Second behavior shall not win, because the first did specify any cardinality + ifMock.method() >> 2 + when: + def result = ifMock.method() + then: + result == 1 + when: + result = ifMock.method() + then: + result == 1 + } + + def "two interactions with first with cardinality"() { + given: + 1 * ifMock.method() >> 1 + ifMock.method() >> 2 + when: + def result = ifMock.method() + then: + result == 1 + when: + result = ifMock.method() + then: + result == 2 + when: + result = ifMock.method() + then: "Additional calls are allowed, because the second has no cardinality" + result == 2 + } + + def "given block two interactions with cardinality after each other"() { + given: + 1 * ifMock.method() >> 1 + 1 * ifMock.method() >> 2 + when: + def result = ifMock.method() + then: + result == 1 + when: + result = ifMock.method() + then: + result == 2 + } + + def "when block two interactions with cardinality after each other"() { + when: + 1 * ifMock.method() >> 1 + 1 * ifMock.method() >> 2 + def result = ifMock.method() + then: + result == 1 + when: + result = ifMock.method() + then: + result == 2 + } + + def "then block two interactions with cardinality after each other"() { + when: + def result1 = ifMock.method() + def result2 = ifMock.method() + then: + 1 * ifMock.method() >> 1 + 1 * ifMock.method() >> 2 + result1 == 1 + result2 == 2 + } + + def "two then blocks two interactions with cardinality after each other"() { + when: + def result1 = ifMock.method() + def result2 = ifMock.method() + then: + 1 * ifMock.method() >> 1 + then: + 1 * ifMock.method() >> 2 + result1 == 1 + result2 == 2 + } + + def "setup interaction"() { + when: + def result = ifMock.methodFromSetup() + then: + result == 1 + } + + @Issue("https://github.com/spockframework/spock/issues/962") + def "given interaction overrides setup, because nothing had cardinality"() { + given: + ifMock.methodFromSetup() >> 2 + when: + def result = ifMock.methodFromSetup() + then: + result == 2 + } + + @Issue("https://github.com/spockframework/spock/issues/962") + def "when interaction overrides setup, because nothing had cardinality"() { + when: + ifMock.methodFromSetup() >> 2 + def result = ifMock.methodFromSetup() + then: + result == 2 + } + + @Issue("https://github.com/spockframework/spock/issues/962") + def "when interaction overrides given, because nothing had cardinality"() { + given: + ifMock.method() >> 2 + when: + ifMock.method() >> 3 + def result = ifMock.method() + then: + result == 3 + } + + @Issue("https://github.com/spockframework/spock/issues/962") + def "when interaction overrides given ans setup, because nothing had cardinality"() { + given: + ifMock.methodFromSetup() >> 2 + when: + ifMock.methodFromSetup() >> 3 + def result = ifMock.methodFromSetup() + then: + result == 3 + } + + @FailsWith(TooFewInvocationsError) + def "given interaction shall not override setup, because given has cardinality"() { + given: "the given block can never be matched, because the setup defines it without cardinality" + 1 * ifMock.methodFromSetup() >> 2 + when: + def result = ifMock.methodFromSetup() + then: + result == 1 + } + + def "then block interaction without cardinality overrides given"() { + given: + ifMock.method() >> 2 + when: + def result = ifMock.method() + then: + ifMock.method() >> 3 + result == 3 + } + + @PendingFeature(reason = "https://github.com/spockframework/spock/pull/1761") + def "then block interaction without cardinality overrides when"() { + when: + ifMock.method() >> 2 + def result = ifMock.method() + then: + ifMock.method() >> 3 + result == 3 + } + + def "then block interaction with cardinality overrides given"() { + given: + ifMock.method() >> 2 + when: + def result = ifMock.method() + then: + 1 * ifMock.method() >> 3 + result == 3 + } + + def "then block interaction with cardinality overrides when"() { + when: + ifMock.method() >> 2 + def result = ifMock.method() + then: + 1 * ifMock.method() >> 3 + result == 3 + } + + def "then block interaction overrides setup"() { + when: + def result = ifMock.methodFromSetup() + then: + 1 * ifMock.methodFromSetup() >> 2 + result == 2 + } + + def "given interaction shall not override setup, because given has cardinality and setup has cardinality"() { + given: + runner.addClassImport(InterfaceType) + runner.runSpecBody(""" + def ifMock = Mock(InterfaceType) + + def setup() { + 1 * ifMock.methodFromSetup() >> 1 + } + + def "test"() { + given: + 1 * ifMock.methodFromSetup() >> 2 + when: + def result = ifMock.methodFromSetup() + then: + result == 1 + when: + result = ifMock.methodFromSetup() + then: + result == 2 + } +""") + } + + def "Positional Arg override"() { + given: + ifMock.methodArg("A") >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 2 + } + + def "type arg override"() { + given: + ifMock.methodArg(_ as String) >> 1 + ifMock.methodArg(_ as String) >> 2 + expect: + ifMock.methodArg("A") == 2 + } + + def "type arg wrong type override"() { + given: + ifMock.methodArg(_ as String) >> 1 + ifMock.methodArg(_ as Object) >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "type arg exact method do no override"() { + given: + ifMock.methodArg(_ as String) >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Wildcard method override"() { + given: + ifMock._ >> 1 + ifMock._ >> 2 + expect: + ifMock.methodArg("A") == 2 + } + + def "Wildcard Exact method do not override"() { + given: + ifMock.methodArg("A") >> 1 + ifMock._ >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Exact method Wildcard do not override"() { + given: + ifMock._ >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Exact method target constraint do not override"() { + given: + ifMock.methodArg("A") >> 1 + _.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "target constraint exact method do not override"() { + given: + _.methodArg("A") >> 1 + ifMock.methodArg("A") >> 2 + + expect: + ifMock.methodArg("A") == 1 + } + + def "Regex method override"() { + given: + ifMock."method.*"(_) >> 1 + ifMock."method.*"(_) >> 2 + expect: + ifMock.methodArg("A") == 2 + } + + def "method explicit method regex do not override"() { + given: + ifMock.methodArg("A") >> 1 + ifMock."method.*"(_) >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "method regex method explicit do not override"() { + given: + ifMock."method.*"(_) >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Spread wildcard args override"() { + given: + ifMock.methodArg(*_) >> 1 + ifMock.methodArg(*_) >> 2 + expect: + ifMock.methodArg("A") == 2 + } + + def "Spread wildcard exact method do no override"() { + given: + ifMock.methodArg(*_) >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "property override"() { + given: + ifMock.propA >> 1 + ifMock.propA >> 2 + expect: + ifMock.propA == 2 + } + + def "property regex override"() { + given: + ifMock."prop.*" >> 1 + ifMock."prop.*" >> 2 + expect: + ifMock.propA == 2 + } + + def "property explicit property regex do not override"() { + given: + ifMock.propA >> 1 + ifMock."prop.*" >> 2 + expect: + ifMock.propA == 1 + } + + def "property regex property explicit do not override"() { + given: + ifMock."prop.*" >> 1 + ifMock.propA >> 2 + expect: + ifMock.propA == 1 + } + + def "Positional Arg negate override"() { + given: + ifMock.methodArg(!"A") >> 1 + ifMock.methodArg(!"A") >> 2 + expect: + ifMock.methodArg("B") == 2 + } + + def "Positional Arg negate exact do not override"() { + given: + ifMock.methodArg(!"A") >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("B") == 1 + } + + def "Positional Arg Wildcard Args do not override"() { + given: + ifMock.methodArg("A") >> 1 + ifMock.methodArg(_) >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Named Arg override"() { + given: + ifMock.methodNamedArg(arg: "A") >> 1 + ifMock.methodNamedArg(arg: "A") >> 2 + expect: + ifMock.methodNamedArg(arg: "A") == 2 + } + + def "Named Arg do not override"() { + given: + ifMock.methodNamedArg(arg: "A") >> 1 + ifMock.methodNamedArg(arg: "B") >> 2 + expect: + ifMock.methodNamedArg(arg: "A") == 1 + } + + def "Code Arg do not override"() { + given: + ifMock.methodArg {} >> 1 + ifMock.methodArg {} >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Code Arg Exact arg do not override"() { + given: + ifMock.methodArg("A") >> 1 + ifMock.methodArg {} >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Code Arg Exact arg inverse do not override"() { + given: + ifMock.methodArg {} >> 1 + ifMock.methodArg("A") >> 2 + expect: + ifMock.methodArg("A") == 1 + } + + def "Named Arg Positional Arg do not override"() { + given: + ifMock.methodNamedArg(arg: "A") >> 1 + ifMock.methodNamedArg("A") >> 2 + expect: + ifMock.methodNamedArg(arg: "A") == 1 + } + + def "Named Arg Positional arg inverse do not override"() { + given: + ifMock.methodNamedArg("A") >> 1 + ifMock.methodNamedArg(arg: "A") >> 2 + expect: + ifMock.methodNamedArg(arg: "A") == 2 + } + + interface InterfaceType { + int methodFromSetup() + + int method() + + int methodArg(String arg) + + int methodNamedArg(Map arg) + + int getPropA(); + } +} diff --git a/spock-specs/src/test/groovy/org/spockframework/util/CollectionUtilSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/util/CollectionUtilSpec.groovy index 17ec0ac4c0..79ec08c8bc 100644 --- a/spock-specs/src/test/groovy/org/spockframework/util/CollectionUtilSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/util/CollectionUtilSpec.groovy @@ -16,6 +16,8 @@ package org.spockframework.util import spock.lang.* +import java.util.function.BiPredicate + class CollectionUtilSpec extends Specification { def "copy an array"() { def array = [1, 2, 3] as Object[] @@ -125,4 +127,23 @@ class CollectionUtilSpec extends Specification { then: l == [1, 2] } + + private static final Closure EQUALS = { Object x, Object y -> x == y } + + def "areListsEqual"(List l1, List l2, BiPredicate func, boolean expected) { + expect: + CollectionUtil.areListsEqual(l1, l2, func) == expected + where: + l1 | l2 | func | expected + [] | [] | EQUALS | true + ["A"] | [] | EQUALS | false + ["A"] | ["B"] | EQUALS | false + ["A", "B"] | ["A", "B"] | EQUALS | true + [] | ["A"] | EQUALS | false + ["A"] | ["A"] | EQUALS | true + [1] | [1] | EQUALS | true + [] | [] | { x, y -> false } | true + [1] | [1] | { x, y -> false } | false + [1, 2] | [1] | { x, y -> true } | false + } }