diff --git a/docs/src/main/asciidoc/verifier/contract.adoc b/docs/src/main/asciidoc/verifier/contract.adoc index 6d1647a16d..caedb24906 100644 --- a/docs/src/main/asciidoc/verifier/contract.adoc +++ b/docs/src/main/asciidoc/verifier/contract.adoc @@ -282,6 +282,10 @@ match the regex for ISO Time be of the same type as the type defined in the body of the response in the contract. `byType` can take a closure where you can set `minOccurrence` and `maxOccurrence`. That way you can assert on the size of the collection. +- `byCommand(...)` - the value taken from the response via the provided JSON Path will be +passed as an input to the custom method that you're providing. E.g. `byCommand('foo($it)')` +will result in calling a `foo` method to which the value matching the JSON Path will get + passed. Let's take a look at the following example: @@ -311,38 +315,43 @@ assertions and the one from matchers with an `and` section): [source,java,indent=0] ---- - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/json") - .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}"); + // given: + MockMvcRequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}"); - // when: - ResponseOptions response = given().spec(request) - .get("/get"); + // when: + ResponseOptions response = given().spec(request) + .get("/get"); - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo"); - // and: - assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}"); - assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123); - assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*"); - assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc"); - assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?"); - assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)"); - assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); - assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); - assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); - assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class); - assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isGreaterThanOrEqualTo(1); - assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isLessThanOrEqualTo(3); - assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isBetween(1, 3); + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo"); + // and: + assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}"); + assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123); + assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*"); + assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc"); + assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?"); + assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)"); + assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); + assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class); + assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isGreaterThanOrEqualTo(1); + assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isLessThanOrEqualTo(3); + assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isBetween(1, 3); + assertThat((Object) parsedJson.read("$.valueWithMinEmpty")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMinEmpty", java.util.Collection.class).size()).isGreaterThanOrEqualTo(0); + assertThat((Object) parsedJson.read("$.valueWithMaxEmpty")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMaxEmpty", java.util.Collection.class).size()).isLessThanOrEqualTo(0); + assertThatValueIsANumber(parsedJson.read("$.duck")); ---- and the WireMock stub like this: diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy index 2f5b932d96..2fc157ef98 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy @@ -17,7 +17,6 @@ package org.springframework.cloud.contract.spec.internal import groovy.transform.CompileStatic - /** * Represents a property that will become an executable method in the * generated tests @@ -27,7 +26,7 @@ import groovy.transform.CompileStatic @CompileStatic class ExecutionProperty { - private static final String PLACEHOLDER_VALUE = '\\$it' + private static final String PLACEHOLDER_VALUE = '$it' final String executionCommand @@ -40,7 +39,7 @@ class ExecutionProperty { * the code that represents that method execution */ String insertValue(String valueToInsert) { - return executionCommand.replaceAll(PLACEHOLDER_VALUE, valueToInsert) + return executionCommand.replace(PLACEHOLDER_VALUE, valueToInsert) } @Override diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy index 88704faff4..4668869eff 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy @@ -38,10 +38,14 @@ enum MatchingType { * Verification if the value for the given path matches the * provided regex */ - REGEX + REGEX, + /** + * The user can provide custom command to execute + */ + COMMAND static boolean regexRelated(MatchingType type) { - if (type == EQUALITY || type == TYPE ) { + if (type == EQUALITY || type == TYPE || type == COMMAND ) { return false } return true diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy index b890e23f20..2e22bce084 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy @@ -18,6 +18,10 @@ class ResponseBodyMatchers extends BodyMatchers { return new MatchingTypeValue(type: MatchingType.TYPE) } + MatchingTypeValue byCommand(String execute) { + return new MatchingTypeValue(MatchingType.COMMAND, new ExecutionProperty(execute)) + } + MatchingTypeValue byType(@DelegatesTo(MatchingTypeValueHolder) Closure closure) { MatchingTypeValueHolder matchingTypeValue = new MatchingTypeValueHolder() closure.delegate = matchingTypeValue diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy index 6ad073e1df..9a85626300 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy @@ -32,4 +32,16 @@ class ExecutionPropertySpec extends Specification { 'commandToExecute(someObject.itsValue)' == commandWithInsertedValue } + def 'should insert passed value with a $ sign in place of $it placeholder'() { + given: + String commandToExecute = 'commandToExecute($it)' + ExecutionProperty executionProperty = new ExecutionProperty(commandToExecute) + and: + String valueToInsert = '$.someObject.itsValue' + when: + String commandWithInsertedValue = executionProperty.insertValue(valueToInsert) + then: + 'commandToExecute($.someObject.itsValue)' == commandWithInsertedValue + } + } diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy index 6673e46123..9abd39202e 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy @@ -13,6 +13,7 @@ class MatchingTypeSpec extends Specification { type | expected MatchingType.EQUALITY | false MatchingType.TYPE | false + MatchingType.COMMAND | false MatchingType.REGEX | true MatchingType.DATE | true MatchingType.TIME | true diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy index 5fb4499990..8d0919bafe 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy @@ -31,7 +31,6 @@ import java.util.regex.Pattern import static groovy.json.StringEscapeUtils.escapeJava import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT import static org.springframework.cloud.contract.verifier.util.ContentUtils.getJavaMultipartFileParameterContent - /** * Root class for JUnit method building * @@ -88,7 +87,7 @@ abstract class JUnitMethodBodyBuilder extends RequestProcessingMethodBodyBuilder @Override protected void processBodyElement(BlockBuilder blockBuilder, String property, ExecutionProperty exec) { - blockBuilder.addLine("${exec.insertValue("parsedJson.read(\"\\\$$property\")")};") + blockBuilder.addLine("${exec.insertValue("parsedJson.read(\"\$$property\")")};") } @Override @@ -104,14 +103,18 @@ abstract class JUnitMethodBodyBuilder extends RequestProcessingMethodBodyBuilder @Override protected void processBodyElement(BlockBuilder blockBuilder, String property, Map.Entry entry) { - processBodyElement(blockBuilder, property + getMapKeyReferenceString(entry), entry.value) + processBodyElement(blockBuilder, getMapKeyReferenceString(property, entry), entry.value) } - private String getMapKeyReferenceString(Map.Entry entry) { + private String getMapKeyReferenceString(String property, Map.Entry entry) { if (entry.value instanceof ExecutionProperty) { - return "." + entry.key + return provideProperJsonPathNotation(property) + "." + entry.key } - return """.get(\\\"$entry.key\\\")""" + return property + """.get(\\\"$entry.key\\\")""" + } + + private String provideProperJsonPathNotation(String property) { + return property.replaceAll('(get\\(\\\\")(.*)(\\\\"\\))', '$2') } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy index 478de9f577..520d93d876 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy @@ -312,40 +312,59 @@ abstract class MethodBodyBuilder { bb.startBlock() // for the rest we'll do JsonPath matching in brute force bodyMatchers.jsonPathMatchers().each { - if (it.value() || it.matchingType() == MatchingType.EQUALITY) { - String path = quotedAndEscaped(it.path()) - Object retrievedValue = value(copiedBody, it) - String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString() - if (arrayRelated(path) && MatchingType.regexRelated(it.matchingType())) { - buildCustomMatchingConditionForEachElement(bb, path, valueAsParam) - } else { - String comparisonMethod = it.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches" - String classToCastTo = "${retrievedValue.class.simpleName}.class" - String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})" - bb.addLine(postProcessJsonPathCall(method)) - } - addColonIfRequired(bb) + if (MatchingType.regexRelated(it.matchingType()) || it.matchingType() == MatchingType.EQUALITY) { + methodForEqualityCheck(it, bb, copiedBody) + } else if (it.matchingType() == MatchingType.COMMAND) { + methodForCommandExecution(it, bb, copiedBody) } else { - Object elementFromBody = value(copiedBody, it) - if (it.minTypeOccurrence() != null || it.maxTypeOccurrence() != null) { - if (arrayRelated(it.path())) { - throw new UnsupportedOperationException("Version 1.0.x doesn't support checking sizes when JSON Path contains [*]. " + - "For more information check out https://github.com/spring-cloud/spring-cloud-contract/issues/217 . " + - "Please upgrade to the latest version of Spring Cloud Contract for this feature.") - } - checkType(bb, it, elementFromBody) - String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, java.util.Collection.class).size()).${sizeCheckMethod(it)}" - bb.addLine(postProcessJsonPathCall(method)) - addColonIfRequired(bb) - } else { - checkType(bb, it, elementFromBody) - } + methodForTypeCheck(it, bb, copiedBody) } } } processBodyElement(bb, "", convertedResponseBody) } + protected void methodForEqualityCheck(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) { + String path = quotedAndEscaped(bodyMatcher.path()) + Object retrievedValue = value(copiedBody, bodyMatcher) + String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString() + if (arrayRelated(path) && MatchingType.regexRelated(bodyMatcher.matchingType())) { + buildCustomMatchingConditionForEachElement(bb, path, valueAsParam) + } else { + String comparisonMethod = bodyMatcher.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches" + String classToCastTo = "${retrievedValue.class.simpleName}.class" + String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})" + bb.addLine(postProcessJsonPathCall(method)) + } + addColonIfRequired(bb) + } + + protected void methodForCommandExecution(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) { + String path = quotedAndEscaped(bodyMatcher.path()) + // assert that path exists + retrieveObjectByPath(copiedBody, bodyMatcher.path()) + ExecutionProperty property = bodyMatcher.value() as ExecutionProperty + bb.addLine(postProcessJsonPathCall(property.insertValue("parsedJson.read(${path})"))) + addColonIfRequired(bb) + } + + protected void methodForTypeCheck(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) { + Object elementFromBody = value(copiedBody, bodyMatcher) + if (bodyMatcher.minTypeOccurrence() != null || bodyMatcher.maxTypeOccurrence() != null) { + if (arrayRelated(bodyMatcher.path())) { + throw new UnsupportedOperationException("Version 1.0.x doesn't support checking sizes when JSON Path contains [*]. " + + "For more information check out https://github.com/spring-cloud/spring-cloud-contract/issues/217 . " + + "Please upgrade to the latest version of Spring Cloud Contract for this feature.") + } + checkType(bb, bodyMatcher, elementFromBody) + String method = "assertThat(parsedJson.read(${quotedAndEscaped(bodyMatcher.path())}, java.util.Collection.class).size()).${sizeCheckMethod(bodyMatcher)}" + bb.addLine(postProcessJsonPathCall(method)) + addColonIfRequired(bb) + } else { + checkType(bb, bodyMatcher, elementFromBody) + } + } + protected boolean arrayRelated(String path) { return path.contains("[*]") || path.contains("..") } @@ -445,7 +464,7 @@ abstract class MethodBodyBuilder { } private String stripFirstChar(String s) { - return s.substring(1); + return s.substring(1) } /** diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy index 254aa643f1..ea6a8b7130 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy @@ -68,7 +68,7 @@ abstract class SpockMethodRequestProcessingBodyBuilder extends RequestProcessing @Override protected void processBodyElement(BlockBuilder blockBuilder, String property, ExecutionProperty exec) { - blockBuilder.addLine("${exec.insertValue("parsedJson.read('\\\$$property')")}") + blockBuilder.addLine("${exec.insertValue("parsedJson.read('\$$property')")}") } @Override diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy index e41aab47f6..827e05480c 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy @@ -777,7 +777,7 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub builder.appendTo(blockBuilder) def test = blockBuilder.toString() then: - test.contains('assertThatRejectionReasonIsNull(parsedJson.read("$.get("rejectionReason").title"));') + test.contains('assertThatRejectionReasonIsNull(parsedJson.read("$.rejectionReason.title"));') } @Issue('#85') diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy index 8b34add1ef..a9a23ffe92 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy @@ -125,6 +125,8 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements // results in verification of size of array (max 0) maxOccurrence(0) }) + // will execute a method `assertThatValueIsANumber` + jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)')) } headers { contentType(applicationJson()) @@ -279,4 +281,74 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$' } + @Issue('#217') + def "should allow matcher with command to execute [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'GET' + url 'person' + } + response { + status 200 + body([ + "phoneNumbers": [ + number: "foo" + ] + ]) + testMatchers { + jsonPath('$.phoneNumbers[*].number', byCommand('foo($it)')) + } + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() + then: + test.contains('foo(parsedJson.read("' + rootElement + '.phoneNumbers[*].number")') + where: + methodBuilderName | methodBuilder | rootElement + "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | '$' + "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$' + } + + @Issue('#217') + def "should throw an exception when command to execute references a non existing entry in the body [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'GET' + url 'person' + } + response { + status 200 + body([ + "phoneNumbers": [ + number: "foo" + ] + ]) + testMatchers { + jsonPath('$.nonExistingPhoneNumbers[*].number', byCommand('foo($it)')) + } + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + then: + IllegalStateException e = thrown(IllegalStateException) + e.message.contains("Entry for the provided JSON path [\$.nonExistingPhoneNumbers[*].number] doesn't exist in the body") + where: + methodBuilderName | methodBuilder | rootElement + "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | '$' + "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$' + } + } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/internal/ExecutionPropertySpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/internal/ExecutionPropertySpec.groovy deleted file mode 100644 index e63147ad21..0000000000 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/internal/ExecutionPropertySpec.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2013-2017 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.springframework.cloud.contract.verifier.dsl.internal - -import org.springframework.cloud.contract.spec.internal.ExecutionProperty - -import spock.lang.Specification - -class ExecutionPropertySpec extends Specification { - - def 'should insert passed value in place of $it placeholder'() { - given: - String commandToExecute = 'commandToExecute($it)' - ExecutionProperty executionProperty = new ExecutionProperty(commandToExecute) - and: - String valueToInsert = 'someObject.itsValue' - when: - String commandWithInsertedValue = executionProperty.insertValue(valueToInsert) - then: - 'commandToExecute(someObject.itsValue)' == commandWithInsertedValue - } - -}