Adding an option to validate children nodes in an array (#219)
without this change it's impossible to assert with response matchers all elements of an array with this change we're using AssertJs conditions in case the pattern contains [*] fixes #217
This commit is contained in:
committed by
GitHub
parent
8449382110
commit
ab7fe13aac
@@ -342,7 +342,7 @@ assertions and the one from matchers with an `and` section):
|
||||
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()).isStrictlyBetween(1, 3);
|
||||
assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isBetween(1, 3);
|
||||
----
|
||||
|
||||
and the WireMock stub like this:
|
||||
|
||||
@@ -39,4 +39,11 @@ enum MatchingType {
|
||||
* provided regex
|
||||
*/
|
||||
REGEX
|
||||
|
||||
static boolean regexRelated(MatchingType type) {
|
||||
if (type == EQUALITY || type == TYPE ) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class MatchingTypeSpec extends Specification {
|
||||
def "should return [#expected] for type [#type]"() {
|
||||
expect:
|
||||
MatchingType.regexRelated(type) == expected
|
||||
where:
|
||||
type | expected
|
||||
MatchingType.EQUALITY | false
|
||||
MatchingType.TYPE | false
|
||||
MatchingType.REGEX | true
|
||||
MatchingType.DATE | true
|
||||
MatchingType.TIME | true
|
||||
MatchingType.TIMESTAMP | true
|
||||
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,11 @@ class BlockBuilder {
|
||||
}
|
||||
|
||||
BlockBuilder addAtTheEnd(String toAdd) {
|
||||
if (builder.charAt(builder.length() - 1) as String == '\n') {
|
||||
String lastChar = builder.charAt(builder.length() - 1) as String
|
||||
String secondLastChar = builder.length() >= 2 ? builder.charAt(builder.length() - 2) as String : ""
|
||||
if (endsWithNewLine(lastChar) && aSpecialSign(secondLastChar, toAdd)) {
|
||||
return this
|
||||
} else if (endsWithNewLine(lastChar) && !aSpecialSign(secondLastChar, toAdd)) {
|
||||
builder.replace(builder.length() - 1, builder.length(), toAdd)
|
||||
builder << '\n'
|
||||
} else {
|
||||
@@ -99,6 +103,17 @@ class BlockBuilder {
|
||||
return this
|
||||
}
|
||||
|
||||
private boolean endsWithNewLine(String character) {
|
||||
return character as String == '\n'
|
||||
}
|
||||
|
||||
private boolean aSpecialSign(String character, String toAdd) {
|
||||
if (!character) {
|
||||
return false
|
||||
}
|
||||
return character == "{" || character == toAdd
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return builder.toString()
|
||||
|
||||
@@ -313,13 +313,17 @@ abstract class MethodBodyBuilder {
|
||||
// for the rest we'll do JsonPath matching in brute force
|
||||
bodyMatchers.jsonPathMatchers().each {
|
||||
if (it.value() || it.matchingType() == MatchingType.EQUALITY) {
|
||||
String comparisonMethod = it.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches"
|
||||
String path = quotedAndEscaped(it.path())
|
||||
Object retrievedValue = value(copiedBody, it)
|
||||
String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString()
|
||||
String classToCastTo = "${retrievedValue.class.simpleName}.class"
|
||||
String path = quotedAndEscaped(it.path())
|
||||
String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})"
|
||||
bb.addLine(postProcessJsonPathCall(method))
|
||||
if (path.contains("[*]") && 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)
|
||||
} else {
|
||||
Object elementFromBody = value(copiedBody, it)
|
||||
@@ -337,6 +341,23 @@ abstract class MethodBodyBuilder {
|
||||
processBodyElement(bb, "", convertedResponseBody)
|
||||
}
|
||||
|
||||
protected void buildCustomMatchingConditionForEachElement(BlockBuilder bb, String path, String valueAsParam) {
|
||||
String method = "assertThat(parsedJson.read(${path}, java.util.Collection.class)).as(\"All elements match regex\").are("
|
||||
String newCondition = "new org.assertj.core.api.Condition<Object>() {"
|
||||
String overriddenMethod = "@Override public boolean matches(Object o) {"
|
||||
String matches = "return ((String)o).matches(${valueAsParam})"
|
||||
String methodEnd = "}"
|
||||
String classEnd = "})"
|
||||
bb.addLine(postProcessJsonPathCall(method))
|
||||
bb.startBlock().startBlock().addLine(newCondition)
|
||||
bb.startBlock().addLine(overriddenMethod)
|
||||
bb.startBlock().addLine(postProcessJsonPathCall(matches))
|
||||
addColonIfRequired(bb)
|
||||
bb.endBlock().addLine(methodEnd)
|
||||
bb.endBlock().addLine(classEnd)
|
||||
bb.endBlock().endBlock()
|
||||
}
|
||||
|
||||
protected Object value(def body, BodyMatcher bodyMatcher) {
|
||||
if (bodyMatcher.matchingType() == MatchingType.EQUALITY || !bodyMatcher.value()) {
|
||||
return retrieveObjectByPath(body, bodyMatcher.path())
|
||||
@@ -374,7 +395,7 @@ abstract class MethodBodyBuilder {
|
||||
|
||||
protected String sizeCheckMethod(BodyMatcher bodyMatcher) {
|
||||
if (bodyMatcher.minTypeOccurrence() != null && bodyMatcher.maxTypeOccurrence() != null) {
|
||||
return "isStrictlyBetween(${bodyMatcher.minTypeOccurrence()}, ${bodyMatcher.maxTypeOccurrence()})"
|
||||
return "isBetween(${bodyMatcher.minTypeOccurrence()}, ${bodyMatcher.maxTypeOccurrence()})"
|
||||
} else if (bodyMatcher.minTypeOccurrence() != null ) {
|
||||
return "isGreaterThanOrEqualTo(${bodyMatcher.minTypeOccurrence()})"
|
||||
} else if (bodyMatcher.maxTypeOccurrence() != null) {
|
||||
|
||||
@@ -74,7 +74,13 @@ class JsonToJsonPathsConverter {
|
||||
DocumentContext context = JsonPath.parse(jsonCopy)
|
||||
if (bodyMatchers?.hasMatchers()) {
|
||||
bodyMatchers.jsonPathMatchers().each { BodyMatcher matcher ->
|
||||
context.delete(matcher.path())
|
||||
try {
|
||||
context.delete(matcher.path())
|
||||
} catch (RuntimeException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exception occurred while trying to delete path [${matcher.path()}]", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return jsonCopy
|
||||
|
||||
@@ -153,7 +153,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMax")).isInstanceOf(java.util.List.class)')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMax", java.util.Collection.class).size()).isLessThanOrEqualTo(3)')
|
||||
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMinMax")).isInstanceOf(java.util.List.class)')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMinMax", java.util.Collection.class).size()).isStrictlyBetween(1, 3)')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMinMax", java.util.Collection.class).size()).isBetween(1, 3)')
|
||||
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMinEmpty")).isInstanceOf(java.util.List.class)')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMinEmpty", java.util.Collection.class).size()).isGreaterThanOrEqualTo(0)')
|
||||
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMaxEmpty")).isInstanceOf(java.util.List.class)')
|
||||
@@ -169,4 +169,77 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$'
|
||||
}
|
||||
|
||||
@Issue('#217')
|
||||
def "should allow complex matchers for [#methodBuilderName]"() {
|
||||
given:
|
||||
Contract contractDsl = Contract.make {
|
||||
request {
|
||||
method 'GET'
|
||||
url 'person'
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body([
|
||||
"firstName": "Jane",
|
||||
"lastName": "Doe",
|
||||
"isAlive": true,
|
||||
"address": [
|
||||
"postalCode": "98101",
|
||||
],
|
||||
"phoneNumbers": [
|
||||
[
|
||||
"type": "home",
|
||||
"number": "999 999-9999",
|
||||
]
|
||||
],
|
||||
"gender": [
|
||||
"type": "female",
|
||||
],
|
||||
"children": [
|
||||
[
|
||||
"firstName": "Kid",
|
||||
"age": 55,
|
||||
]
|
||||
],
|
||||
])
|
||||
testMatchers {
|
||||
jsonPath('$.phoneNumbers', byType {
|
||||
minOccurrence(0) // min occurrence of 1
|
||||
maxOccurrence(4) // max occurrence of 3
|
||||
})
|
||||
jsonPath('$.phoneNumbers[*].number', byRegex("^[0-9]{3} [0-9]{3}-[0-9]{4}\$"))
|
||||
jsonPath('$..number', byRegex("^[0-9]{3} [0-9]{3}-[0-9]{4}\$"))
|
||||
}
|
||||
|
||||
headers {
|
||||
contentType('application/json')
|
||||
}
|
||||
}
|
||||
}
|
||||
MethodBodyBuilder builder = methodBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.appendTo(blockBuilder)
|
||||
def test = blockBuilder.toString()
|
||||
then:
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.phoneNumbers[*].number", java.util.Collection.class)).as("All elements match regex").are(')
|
||||
test.contains('new org.assertj.core.api.Condition<Object>() {')
|
||||
test.contains('@Override public boolean matches(Object o) {')
|
||||
test.contains('return ((String)o).matches("^[0-9]{3} [0-9]{3}-[0-9]{4}' + rootElement + '")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '..number", String.class)).matches("^[0-9]{3} [0-9]{3}-[0-9]{4}' + rootElement + '")')
|
||||
!test.contains('cursor')
|
||||
and:
|
||||
try {
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, blockBuilder.toString())
|
||||
} catch(NoClassDefFoundError error) {
|
||||
// that's actually expected since we're creating an anonymous class
|
||||
}
|
||||
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) } | '$'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user