Adds handling for empty list and empty map response body (#1556)

Fixes gh-1423
This commit is contained in:
bono007
2020-11-17 01:31:53 -06:00
committed by GitHub
parent 3477371021
commit b0e33b3e90
4 changed files with 159 additions and 8 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.verifier.builder;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@@ -102,6 +103,12 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
Object copiedBody = cloneBody(convertedResponseBody);
convertedResponseBody = JsonToJsonPathsConverter
.removeMatchingJsonPaths(convertedResponseBody, bodyMatchers);
// If it was a map or list where all elements covered by matchers - json paths
// should not include empty check
boolean includeEmptyCheck = !mapOrListBodyReducedToEmpty(copiedBody,
convertedResponseBody);
// remove quotes from fromRequest objects before picking json paths
TestSideRequestTemplateModel templateModel = hasRequestBody()
? TestSideRequestTemplateModel.from(contract.getRequest()) : null;
@@ -109,7 +116,8 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
returnReferencedEntries(templateModel), parsingClosure);
JsonPaths jsonPaths = new JsonToJsonPathsConverter(assertJsonSize)
.transformToJsonPathWithTestsSideValues(convertedResponseBody,
parsingClosure);
parsingClosure, includeEmptyCheck);
DocumentContext finalParsedRequestBody = parsedRequestBody;
jsonPaths.forEach(it -> {
String method = it.method();
@@ -123,6 +131,20 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
return convertedResponseBody;
}
private boolean mapOrListBodyReducedToEmpty(Object originalBody,
Object convertedBody) {
int origSize = originalBody instanceof Map ? ((Map) originalBody).size() : -1;
int convertedSize = convertedBody instanceof Map ? ((Map) convertedBody).size()
: -1;
if (origSize > 0 && convertedSize == 0) {
return true;
}
origSize = originalBody instanceof List ? ((List) originalBody).size() : -1;
convertedSize = convertedBody instanceof List ? ((List) convertedBody).size()
: -1;
return (origSize > 0 && convertedSize == 0);
}
private boolean hasRequestBody() {
return contract.getRequest() != null && contract.getRequest().getBody() != null;
}

View File

@@ -320,13 +320,15 @@ class JsonToJsonPathsConverter {
}
JsonPaths transformToJsonPathWithTestsSideValues(def json,
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE) {
return transformToJsonPathWithValues(json, SERVER_SIDE, parsingClosure)
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE,
boolean includeEmptyCheck = false) {
return transformToJsonPathWithValues(json, SERVER_SIDE, parsingClosure, includeEmptyCheck)
}
JsonPaths transformToJsonPathWithStubsSideValues(def json,
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE) {
return transformToJsonPathWithValues(json, CLIENT_SIDE, parsingClosure)
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE,
boolean includeEmptyCheck = false) {
return transformToJsonPathWithValues(json, CLIENT_SIDE, parsingClosure, includeEmptyCheck)
}
static JsonPaths transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(def json,
@@ -336,11 +338,11 @@ class JsonToJsonPathsConverter {
}
private JsonPaths transformToJsonPathWithValues(def json, boolean clientSide,
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE) {
if (!json) {
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE,
boolean includeEmptyCheck = false) {
if (json == null || (!json && !includeEmptyCheck)) {
return new JsonPaths()
}
JsonPaths pathsAndValues = [] as Set
Object convertedJson = MapConverter.
getClientOrServerSideValues(json, clientSide, parsingClosure)
Object jsonWithPatterns = ContentUtils.
@@ -349,6 +351,11 @@ class JsonToJsonPathsConverter {
new DelegatingJsonVerifiable(JsonAssertion.
assertThat(JsonOutput.toJson(jsonWithPatterns))
.withoutThrowingException())
JsonPaths pathsAndValues = [] as Set
if (isRootElement(methodBufferingJsonPathVerifiable) && !json) {
pathsAndValues.add(methodBufferingJsonPathVerifiable.isEmpty())
return pathsAndValues
}
traverseRecursivelyForKey(jsonWithPatterns, methodBufferingJsonPathVerifiable,
{ MethodBufferingJsonVerifiable key, Object value ->
if (value instanceof ExecutionProperty || !(key instanceof FinishedDelegatingJsonVerifiable)) {

View File

@@ -578,6 +578,66 @@ class SpringTestMethodBodyBuildersSpec extends Specification implements WireMock
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
@Issue('#1423')
def 'should generate assertions for a response body containing an empty list with #methodBuilderName'() {
given:
Contract contractDsl = Contract.make {
request {
method "GET"
url "/url"
}
response {
status OK()
body("[]")
}
}
methodBuilder()
when:
String test = singleTestGenerator(contractDsl)
then:
test.contains("""assertThatJson(parsedJson).isEmpty()""")
and:
stubMappingIsValidWireMockStub(contractDsl)
and:
SyntaxChecker.tryToCompile(methodBuilderName, test)
where:
methodBuilderName | methodBuilder
"spock" | { properties.testFramework = TestFramework.SPOCK }
"testng" | { properties.testFramework = TestFramework.TESTNG }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
@Issue('#1423')
def 'should generate assertions for a response body containing an empty map with #methodBuilderName'() {
given:
Contract contractDsl = Contract.make {
request {
method "GET"
url "/url"
}
response {
status OK()
body("{}")
}
}
methodBuilder()
when:
String test = singleTestGenerator(contractDsl)
then:
test.contains("""assertThatJson(parsedJson).isEmpty()""")
and:
stubMappingIsValidWireMockStub(contractDsl)
and:
SyntaxChecker.tryToCompile(methodBuilderName, test)
where:
methodBuilderName | methodBuilder
"spock" | { properties.testFramework = TestFramework.SPOCK }
"testng" | { properties.testFramework = TestFramework.TESTNG }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
def 'should generate regex assertions for map objects in response body with #methodBuilderName'() {
given:
Contract contractDsl = Contract.make {

View File

@@ -434,6 +434,68 @@ response:
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
@Issue('#1423')
def 'should generate assertions for a response body containing an empty list with #methodBuilderName'() {
given:
String contract = """\
---
description: Returns an empty collection
request:
method: GET
urlPath: /url
response:
status: 200
body: []
"""
Contract contractDsl = fromYaml(contract)
methodBuilder()
when:
String test = singleTestGenerator(contractDsl)
then:
test.contains("""assertThatJson(parsedJson).isEmpty()""")
and:
stubMappingIsValidWireMockStub(contractDsl)
and:
SyntaxChecker.tryToCompile(methodBuilderName, test)
where:
methodBuilderName | methodBuilder
"spock" | { properties.testFramework = TestFramework.SPOCK }
"testng" | { properties.testFramework = TestFramework.TESTNG }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
@Issue('#1423')
def 'should generate assertions for a response body containing an empty map with #methodBuilderName'() {
given:
String contract = """\
---
description: Returns an empty map
request:
method: GET
urlPath: /url
response:
status: 200
body: {}
"""
Contract contractDsl = fromYaml(contract)
methodBuilder()
when:
String test = singleTestGenerator(contractDsl)
then:
test.contains("""assertThatJson(parsedJson).isEmpty()""")
and:
stubMappingIsValidWireMockStub(contractDsl)
and:
SyntaxChecker.tryToCompile(methodBuilderName, test)
where:
methodBuilderName | methodBuilder
"spock" | { properties.testFramework = TestFramework.SPOCK }
"testng" | { properties.testFramework = TestFramework.TESTNG }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
}
def 'should generate regex assertions for map objects in response body with #methodBuilderName'() {
given:
String contract = '''\