Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added configurable interaction matching #1219

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package org.spockframework.mock.runtime;

import org.spockframework.mock.*;
import org.spockframework.runtime.RunContext;
import org.spockframework.util.ExceptionUtil;
import spock.config.RunnerConfiguration;

import java.util.*;

Expand All @@ -33,6 +35,7 @@ public class InteractionScope implements IInteractionScope {
private int currentRegistrationZone = 0;
private int currentExecutionZone = 0;
private final Deque<IMockInvocation> previousInvocationsInReverseOrder = new ArrayDeque<>(MAX_PREVIOUS_INVOCATIONS);
private static final RunnerConfiguration configuration = RunContext.get().getConfiguration(RunnerConfiguration.class);

@Override
public void addInteraction(final IMockInteraction interaction) {
Expand Down Expand Up @@ -83,14 +86,18 @@ public void addUnmatchedInvocation(IMockInvocation invocation) {

@Override
public IMockInteraction match(IMockInvocation invocation) {
IMockInteraction firstMatch = null;
IMockInteraction match = null;
for (IMockInteraction interaction : interactions)
if (interaction.matches(invocation)) {
if (!interaction.isExhausted()) return interaction;
if (firstMatch == null) firstMatch = interaction;
if (configuration.matchFirstInteraction) {
if (!interaction.isExhausted()) return interaction;
if (match == null) match = interaction;
} else {
if (!interaction.isExhausted() || match == null) match = interaction;
}
}

return firstMatch;
return match;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* baseClass IntegrationSpec
* }
* filterStackTrace true // this is the default
* matchFirstInteraction true // this is the default
* }
* </pre>
*/
Expand All @@ -38,4 +39,5 @@ public class RunnerConfiguration {
public IncludeExcludeCriteria exclude = new IncludeExcludeCriteria();
public boolean filterStackTrace = true;
public boolean optimizeRunOrder = false;
public boolean matchFirstInteraction = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2020 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
*
* http://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.mock.IMockInteraction
import org.spockframework.mock.runtime.InteractionScope
import org.spockframework.mock.runtime.MockInvocation
import org.spockframework.runtime.RunContext
import spock.config.RunnerConfiguration
import spock.lang.Specification

/**
*
* @author Bouke Nijhuis
*/
class InteractionScopeExhaustion extends Specification {

def "exhausted interactions should be skipped (if possible) for the firstMatch algorithm"() {
expect:
test(match1, isExhausted1, match2, isExhausted2, resultText)

where:
match1 | isExhausted1 || match2 | isExhausted2 || resultText
//false | false || false | false ||
//false | false || false | true ||
false | false || true | false || "2"
false | false || true | true || "2"

//false | true || false | false ||
//false | true || false | true ||
false | true || true | false || "2"
false | true || true | true || "2"

true | false || false | false || "1"
true | false || false | true || "1"
true | false || true | false || "1"
true | false || true | true || "1"

true | true || false | false || "1"
true | true || false | true || "1"
true | true || true | false || "2"
true | true || true | true || "1"
}

def "exhausted interactions should be skipped (if possible) for the lastMatch algorithm"() {
RunContext.get().getConfiguration(RunnerConfiguration.class).matchFirstInteraction = false

expect:
test(match1, isExhausted1, match2, isExhausted2, resultText)

cleanup:
RunContext.get().getConfiguration(RunnerConfiguration.class).matchFirstInteraction = true

where:
match1 | isExhausted1 || match2 | isExhausted2 || resultText
//false | false || false | false ||
//false | false || false | true ||
false | false || true | false || "2"
false | false || true | true || "2"

//false | true || false | false ||
//false | true || false | true ||
false | true || true | false || "2"
false | true || true | true || "2"

true | false || false | false || "1"
true | false || false | true || "1"
true | false || true | false || "2"
true | false || true | true || "1"

true | true || false | false || "1"
true | true || false | true || "1"
true | true || true | false || "2"
true | true || true | true || "1"
}


def test(boolean match1, boolean isExhausted1, boolean match2, boolean isExhausted2, String resultText) {
InteractionScope interactionScope = new InteractionScope()
MockInvocation mockInvocation = Mock()

IMockInteraction mockInteraction1 = Mock()
interactionScope.addInteraction(mockInteraction1)
mockInteraction1.matches(mockInvocation) >> match1
mockInteraction1.isExhausted() >> isExhausted1
mockInteraction1.getText() >> "1"

IMockInteraction mockInteraction2 = Mock()
interactionScope.addInteraction(mockInteraction2)
mockInteraction2.matches(mockInvocation) >> match2
mockInteraction2.isExhausted() >> isExhausted2
mockInteraction2.getText() >> "2"

IMockInteraction mockInteractionResult = interactionScope.match(mockInvocation)

return mockInteractionResult.getText() == resultText
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2020 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
*
* http://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.RunContext
import spock.config.RunnerConfiguration
import spock.lang.Specification

/**
*
* @author Bouke Nijhuis
*/
class InteractionScopeMatching extends Specification {
List list = Mock()

def setup() {
list.size() >> 1
list.size() >> -1
}

def "RunnerConfiguration.matchFirstInteraction should default to true"() {
expect:
RunContext.get().getConfiguration(RunnerConfiguration.class).matchFirstInteraction
}

def "interactions should (by default) use the first match algorithm when determining the stubbed reply"() {
expect: // it to use the first defined reply of the method
list.size() == 1
}

def "interactions should (when specified) use the last match algorithm when determining the stubbed reply"() {
RunContext.get().getConfiguration(RunnerConfiguration.class).matchFirstInteraction = false

expect: // it to use the last defined reply of the method
list.size() == -1

when: // adding an interaction outside of the setup method
list.size() >> -2

then: // I expect it the use the last defined reply of the method
list.size() == -2

cleanup:
RunContext.get().getConfiguration(RunnerConfiguration.class).matchFirstInteraction = true
}
}