From 89eba29a267e3aeb67800f50c5b3e59c2b3be778 Mon Sep 17 00:00:00 2001 From: Roger <587230+rojyates@users.noreply.github.com> Date: Tue, 12 Mar 2024 07:05:44 +1000 Subject: [PATCH] BAEL-7315 Add source code for Spock Argument Capture tutorial (#16083) --- .../capture/ArgumentCaptureDependency.java | 9 + .../spock/capture/ArgumentCaptureSubject.java | 23 ++ .../capture/ArgumentCaptureUnitTest.groovy | 221 ++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureDependency.java create mode 100644 testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureSubject.java create mode 100644 testing-modules/groovy-spock/src/test/groovy/com/baeldung/spock/capture/ArgumentCaptureUnitTest.groovy diff --git a/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureDependency.java b/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureDependency.java new file mode 100644 index 0000000000..5b0637bbca --- /dev/null +++ b/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureDependency.java @@ -0,0 +1,9 @@ +package com.baeldung.spock.capture; + +public class ArgumentCaptureDependency { + + public String catchMe(final String input) { + return "***" + input + "***"; + } + +} \ No newline at end of file diff --git a/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureSubject.java b/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureSubject.java new file mode 100644 index 0000000000..71038016fa --- /dev/null +++ b/testing-modules/groovy-spock/src/main/java/com/baeldung/spock/capture/ArgumentCaptureSubject.java @@ -0,0 +1,23 @@ +package com.baeldung.spock.capture; + +public class ArgumentCaptureSubject { + + ArgumentCaptureDependency calledClass; + + public ArgumentCaptureSubject(ArgumentCaptureDependency calledClass) { + this.calledClass = calledClass; + } + + public String catchMeIfYouCan(final String input) { + return "Received " + input; + } + + public String callOtherClass() { + return calledClass.catchMe("Internal Parameter"); + } + + public String callOtherClass(final String input) { + return calledClass.catchMe(input); + } + +} diff --git a/testing-modules/groovy-spock/src/test/groovy/com/baeldung/spock/capture/ArgumentCaptureUnitTest.groovy b/testing-modules/groovy-spock/src/test/groovy/com/baeldung/spock/capture/ArgumentCaptureUnitTest.groovy new file mode 100644 index 0000000000..df3960d832 --- /dev/null +++ b/testing-modules/groovy-spock/src/test/groovy/com/baeldung/spock/capture/ArgumentCaptureUnitTest.groovy @@ -0,0 +1,221 @@ +package com.baeldung.spock.capture + + +import spock.lang.PendingFeature +import spock.lang.Specification +import spock.lang.Subject +import spock.lang.Title + +@Title("A selection of tests to demonstrate capturing arguments") +class ArgumentCaptureUnitTest extends Specification { + + // 3. Preparing Our Data-Driven Test + def "given a Stub when we invoke it then we capture the stubbed response"() { + given: "an input and a result" + def input = "Input" + def stubbedResponse = "42" + + and: "a Stub for our response" + @Subject + ArgumentCaptureSubject stubClass = Stub() + stubClass.catchMeIfYouCan(_) >> stubbedResponse + + when: "we invoke our Stub's method" + def result = stubClass.catchMeIfYouCan(input) + + then: "we get our stubbed response" + result == stubbedResponse + } + + + // 4. Capturing Arguments - argument list and a stubbed response + def "given a Stub when we invoke it then we capture the list of arguments and return a stubbed result"() { + given: "an input and a result" + def input = "Input" + def stubbedResponse = "42" + + and: "a variable and a Stub with a Closure to capture our arguments" + def captured + @Subject + ArgumentCaptureSubject stubClass = Stub() + stubClass.catchMeIfYouCan(_) >> { arguments -> captured = arguments; stubbedResponse } + + when: "we invoke our method" + def result = stubClass.catchMeIfYouCan(input) + + then: "what we captured matches the input" + captured[0] == input + result == stubbedResponse + } + + + // 4. Capturing Arguments - single argument + def "given a Stub when we invoke it then we capture the argument"() { + given: "an input and a result" + def input = "Input" + + and: "a variable and a Stub with a Closure to capture our arguments" + @Subject + def captured + ArgumentCaptureSubject stubClass = Stub() + stubClass.catchMeIfYouCan(_) >> { arguments -> captured = arguments[0] } + + when: "we invoke our method" + stubClass.catchMeIfYouCan(input) + + then: "what we captured matches the input" + captured == input + } + + + // 4. Capturing Arguments - single argument and a stubbed response + def "given a Stub when we invoke it then we capture the argument and return a stubbed result"() { + given: "an input and a result" + def input = "Input" + def stubbedResponse = "42" + + and: "a variable and a Stub with a Closure to capture our variable and return a stubbed response" + @Subject + def captured + ArgumentCaptureSubject stubClass = Stub() + stubClass.catchMeIfYouCan(_) >> { arguments -> captured = arguments[0]; stubbedResponse } + + when: "we invoke our method" + def result = stubClass.catchMeIfYouCan(input) + + then: "what we captured matches the input and we got our stubbed response" + captured == input + result == stubbedResponse + } + + + // 5. Capturing with Spies - calling real method + def "given a Spy when we invoke it then we capture the argument and then delegate to the real method"() { + given: "an input string" + def input = "Input" + + and: "a variable and a Spy with a Closure to capture the argument and call the underlying method" + def captured + @Subject + ArgumentCaptureSubject spyClass = Spy() + spyClass.catchMeIfYouCan(_) >> { arguments -> captured = arguments[0]; callRealMethod() } + + when: "we invoke our method" + def result = spyClass.catchMeIfYouCan(input) + + then: "what we captured matches the input and our result comes from the real method" + captured == input + result == "Received Input" + } + + // 5. Capturing with Spies - tampering with the arguments + def "given a Spy when we invoke it then we capture the argument and then delegate to the real method with a different value"() { + given: "an input string" + def input = "Input" + + and: "a variable and a Spy with a Closure to capture and change the first argument then call the underlying method" + def captured + @Subject + ArgumentCaptureSubject spyClass = Spy() + spyClass.catchMeIfYouCan(_) >> { arguments -> captured = arguments[0]; callRealMethodWithArgs('Tampered:' + captured) } + + when: "we invoke our method" + def result = spyClass.catchMeIfYouCan(input) + + then: "what we captured matches the input" + captured == input + result == "Received Tampered:Input" + } + + + // 6. Capturing Arguments Using an Injected Mock + def "given an internal method call when we invoke our subject then we capture the internal argument and return the result of the real method"() { + given: "a variable and a Spy with a Closure to capture the first argument and call the underlying method" + def captured + ArgumentCaptureDependency spyClass = Spy() + spyClass.catchMe(_) >> { arguments -> captured = arguments[0]; callRealMethod() } + + and: "our subject with an injected Spy" + @Subject + def argumentCaptureSubject = new ArgumentCaptureSubject(spyClass) + + when: "we invoke our method" + def result = argumentCaptureSubject.callOtherClass() + + then: "what we captured matches the input" + captured == "Internal Parameter" + result == "***Internal Parameter***" + } + + + // 7. Capturing Multiple Invocations + def "given an dynamic Mock when we invoke our subject then we capture the argument for each invocation"() { + given: "a variable for our captured arguments and a mock to capture them" + def capturedStrings = new ArrayList() + ArgumentCaptureDependency mockClass = Mock() + + and: "our subject" + @Subject + def argumentCaptureSubject = new ArgumentCaptureSubject(mockClass) + + when: "we invoke our method" + argumentCaptureSubject.callOtherClass("First") + argumentCaptureSubject.callOtherClass("Second") + + then: "our method was called twice and captured the argument" + 2 * mockClass.catchMe(_ as String) >> { arguments -> + capturedStrings.add(arguments[0]) + } + + and: "our captured list contains an entry for both of our input values" + capturedStrings[0] == "First" + capturedStrings[1] == "Second" + capturedStrings.contains("First") + capturedStrings.contains("Second") + } + + + // 8. Using Multiple then Blocks + def "given a Mock when we invoke our subject twice then our Mock verifies the sequence"() { + given: "a mock" + ArgumentCaptureDependency mockClass = Mock() + + and: "our subject" + @Subject argumentCaptureSubject = new ArgumentCaptureSubject(mockClass) + + when: "we invoke our method" + argumentCaptureSubject.callOtherClass("First") + argumentCaptureSubject.callOtherClass("Second") + + then: "we invoked our Mock with 'First' the first time" + 1 * mockClass.catchMe( "First") + + then: "we invoked our Mock with 'Second' the next time" + 1 * mockClass.catchMe( "Second") + } + + + // 8. Using Multiple then Blocks - demonstrate error message + @PendingFeature(reason = "Remove this annotation to fail this test and see what message Spock issues when invocations occur in the wrong sequence") + def "given a Mock when we invoke our subject in the wrong order then Spock's Mock detects the wrong sequence"() { + given: "an input string" + def input = "Input" + + and: "a mock and a variable for our captured argument" + ArgumentCaptureDependency mockClass = Mock() + + and: "our subject" + @Subject argumentCaptureSubject = new ArgumentCaptureSubject(mockClass) + + when: "we invoke our method" + argumentCaptureSubject.callOtherClass("Second") + argumentCaptureSubject.callOtherClass("First") + + then: "we invoked our Mock with 'First' the first time" + 1 * mockClass.catchMe( "First") + + then: "we invoked our Mock with 'Second' the next time" + 1 * mockClass.catchMe( "Second") + } + +}