Added flattened size assertion and refactored collection assertions

with this change we no longer call the integer assertions on the size. We have our own custom size assertions.
Also we can do a flattened size assertion. If in the jsonpath there is [*] we can finally check the whole size and assert it properly

related to #217
This commit is contained in:
Marcin Grzejszczak
2017-02-09 16:13:28 +01:00
parent a2c48dd1ba
commit 8b91361bc1
4 changed files with 403 additions and 9 deletions

View File

@@ -329,7 +329,7 @@ abstract class MethodBodyBuilder {
Object elementFromBody = value(copiedBody, it)
if (it.minTypeOccurrence() != null || it.maxTypeOccurrence() != null) {
checkType(bb, it, elementFromBody)
String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, java.util.Collection.class).size()).${sizeCheckMethod(it)}"
String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, java.util.Collection.class)).${sizeCheckMethod(it)}"
bb.addLine(postProcessJsonPathCall(method))
addColonIfRequired(bb)
} else {
@@ -382,15 +382,24 @@ abstract class MethodBodyBuilder {
}
protected String sizeCheckMethod(BodyMatcher bodyMatcher) {
String prefix = sizeCheckPrefix(bodyMatcher)
if (bodyMatcher.minTypeOccurrence() != null && bodyMatcher.maxTypeOccurrence() != null) {
return "isBetween(${bodyMatcher.minTypeOccurrence()}, ${bodyMatcher.maxTypeOccurrence()})"
return "${prefix}Between(${bodyMatcher.minTypeOccurrence()}, ${bodyMatcher.maxTypeOccurrence()})"
} else if (bodyMatcher.minTypeOccurrence() != null ) {
return "isGreaterThanOrEqualTo(${bodyMatcher.minTypeOccurrence()})"
return "${prefix}GreaterThanOrEqualTo(${bodyMatcher.minTypeOccurrence()})"
} else if (bodyMatcher.maxTypeOccurrence() != null) {
return "isLessThanOrEqualTo(${bodyMatcher.maxTypeOccurrence()})"
return "${prefix}LessThanOrEqualTo(${bodyMatcher.maxTypeOccurrence()})"
}
}
private String sizeCheckPrefix(BodyMatcher bodyMatcher) {
String prefix = "has"
if (bodyMatcher.path().contains("[*]")) {
prefix = prefix + "Flattened"
}
return prefix + "Size"
}
protected String quotedAndEscaped(String string) {
return '"' + StringEscapeUtils.escapeJava(string) + '"'
}

View File

@@ -1,6 +1,8 @@
package org.springframework.cloud.contract.verifier.assertion;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.assertj.core.api.IterableAssert;
@@ -35,4 +37,114 @@ public class CollectionAssert<ELEMENT> extends IterableAssert<ELEMENT> {
}
return this;
}
/**
* Flattens the collection and checks whether size is greater than or equal to the provided value
* @param size - the flattened collection should have size greater than or equal to this value
* @return this
*/
public CollectionAssert hasFlattenedSizeGreaterThanOrEqualTo(int size) {
isNotNull();
int flattenedSize = flattenedSize(0, this.actual);
if (!(flattenedSize >= size)) {
failWithMessage("The flattened size <%s> is not greater or equal to <%s>", flattenedSize, size);
}
return this;
}
/**
* Flattens the collection and checks whether size is less than or equal to the provided value
* @param size - the flattened collection should have size less than or equal to this value
* @return this
*/
public CollectionAssert hasFlattenedSizeLessThanOrEqualTo(int size) {
isNotNull();
int flattenedSize = flattenedSize(0, this.actual);
if (!(flattenedSize <= size)) {
failWithMessage("The flattened size <%s> is not less or equal to <%s>", flattenedSize, size);
}
return this;
}
/**
* Flattens the collection and checks whether size is between the provided value
* @param lowerBound - the flattened collection should have size greater than or equal to this value
* @param higherBound - the flattened collection should have size less than or equal to this value
* @return this
*/
public CollectionAssert hasFlattenedSizeBetween(int lowerBound, int higherBound) {
isNotNull();
int flattenedSize = flattenedSize(0, this.actual);
if (!(flattenedSize >= lowerBound && flattenedSize <= higherBound)) {
failWithMessage("The flattened size <%s> is not between <%s> and <%s>", flattenedSize, lowerBound, higherBound);
}
return this;
}
/**
* Checks whether size is greater than or equal to the provided value
* @param size - the collection should have size greater than or equal to this value
* @return this
*/
public CollectionAssert hasSizeGreaterThanOrEqualTo(int size) {
isNotNull();
int actualSize = size(this.actual);
if (!(actualSize >= size)) {
failWithMessage("The size <%s> is not greater or equal to <%s>", actualSize, size);
}
return this;
}
/**
* Checks whether size is less than or equal to the provided value
* @param size - the collection should have size less than or equal to this value
* @return this
*/
public CollectionAssert hasSizeLessThanOrEqualTo(int size) {
isNotNull();
int actualSize = size(this.actual);
if (!(actualSize <= size)) {
failWithMessage("The size <%s> is not less or equal to <%s>", actualSize, size);
}
return this;
}
/**
* Checks whether size is between the provided value
* @param lowerBound - the collection should have size greater than or equal to this value
* @param higherBound - the collection should have size less than or equal to this value
* @return this
*/
public CollectionAssert hasSizeBetween(int lowerBound, int higherBound) {
isNotNull();
int size = size(this.actual);
if (!(size >= lowerBound && size <= higherBound)) {
failWithMessage("The size <%s> is not between <%s> and <%s>", size, lowerBound, higherBound);
}
return this;
}
private int flattenedSize(int counter, Object object) {
if (object instanceof Map) {
return counter + ((Map) object).size();
} else if (object instanceof Iterator) {
Iterator iterator = ((Iterator) object);
while (iterator.hasNext()) {
Object next = iterator.next();
counter = flattenedSize(counter, next);
}
return counter;
} else if (object instanceof Collection) {
return flattenedSize(counter, ((Collection) object).iterator());
}
return counter;
}
private int size(Iterable iterable) {
int size = 0;
for (Object value : iterable) {
size++;
}
return size;
}
}

View File

@@ -2,7 +2,9 @@ package org.springframework.cloud.contract.verifier.assertion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.Test;
@@ -55,6 +57,206 @@ public class CollectionAssertTests {
}
}
@Test
public void should_not_throw_an_exception_when_flattened_size_is_greater_than_or_equal_to_provided_size() {
Collection collection = nestedCollection();
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeGreaterThanOrEqualTo(0)
.hasFlattenedSizeGreaterThanOrEqualTo(4);
}
@Test
public void should_throw_an_exception_when_flattened_size_is_not_greater_than_or_equal_to_provided_size() {
Collection collection = nestedCollection();
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeGreaterThanOrEqualTo(5);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The flattened size <4> is not greater or equal to <5>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_flattened_greater_than_or_equal() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeGreaterThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
@Test
public void should_not_throw_an_exception_when_flattened_size_is_less_than_or_equal_to_provided_size() {
Collection collection = nestedCollection();
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeLessThanOrEqualTo(5)
.hasFlattenedSizeLessThanOrEqualTo(4);
}
@Test
public void should_throw_an_exception_when_flattened_size_is_not_less_than_or_equal_to_provided_size() {
Collection collection = nestedCollection();
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeLessThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The flattened size <4> is not less or equal to <1>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_flattened_less_than_or_equal() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeLessThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
@Test
public void should_not_throw_an_exception_when_flattened_size_is_between_the_provided_sizes() {
Collection collection = nestedCollection();
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeBetween(1, 5)
.hasFlattenedSizeBetween(4, 4);
}
@Test
public void should_throw_an_exception_when_flattened_size_is_not_between_the_provided_sizes() {
Collection collection = nestedCollection();
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeBetween(5, 7);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The flattened size <4> is not between <5> and <7>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_flattened_between() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeBetween(1, 2);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
@Test
public void should_not_throw_an_exception_when_size_is_greater_than_or_equal_to_provided_size() {
Collection collection = collection();
SpringCloudContractAssertions.assertThat(collection)
.hasSizeGreaterThanOrEqualTo(0)
.hasSizeGreaterThanOrEqualTo(3);
}
@Test
public void should_throw_an_exception_when_size_is_not_greater_than_or_equal_to_provided_size() {
Collection collection = collection();
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeGreaterThanOrEqualTo(5);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The size <3> is not greater or equal to <5>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_greater_than_or_equal() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeGreaterThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
@Test
public void should_not_throw_an_exception_when_size_is_less_than_or_equal_to_provided_size() {
Collection collection = collection();
SpringCloudContractAssertions.assertThat(collection)
.hasSizeLessThanOrEqualTo(4)
.hasSizeLessThanOrEqualTo(3);
}
@Test
public void should_throw_an_exception_when_size_is_not_less_than_or_equal_to_provided_size() {
Collection collection = collection();
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeLessThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The size <3> is not less or equal to <1>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_less_than_or_equal() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeLessThanOrEqualTo(1);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
@Test
public void should_not_throw_an_exception_when_size_is_between_the_provided_sizes() {
Collection collection = collection();
SpringCloudContractAssertions.assertThat(collection)
.hasSizeBetween(1, 4)
.hasSizeBetween(3, 3);
}
@Test
public void should_throw_an_exception_when_size_is_not_between_the_provided_sizes() {
Collection collection = collection();
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeBetween(5, 7);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("The size <3> is not between <5> and <7>");
}
}
@Test
public void should_throw_an_exception_when_collection_is_null_for_between() {
Collection collection = null;
try {
SpringCloudContractAssertions.assertThat(collection).hasSizeBetween(1, 2);
Assertions.fail("should throw exception");
} catch (AssertionError e) {
Assertions.assertThat(e).hasMessageContaining("Expecting actual not to be null");
}
}
private Collection<String> collection() {
List<String> list = new ArrayList<>();
list.add("a");
@@ -63,4 +265,21 @@ public class CollectionAssertTests {
return list;
}
private Collection nestedCollection() {
List list = new ArrayList<>();
List list1 = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("a", "1");
map1.put("b", "2");
map1.put("c", "3");
List list2 = new ArrayList<>();
Map<String, String> map2 = new HashMap<>();
map2.put("d", "4");
list.add(list1);
list.add(list2);
list1.add(map1);
list2.add(map2);
return list;
}
}

View File

@@ -149,15 +149,15 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
test.contains('assertThat(parsedJson.read("' + rootElement + '.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])")')
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithTypeMatch")).isInstanceOf(java.lang.String.class)')
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMin")).isInstanceOf(java.util.List.class)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMin", java.util.Collection.class).size()).isGreaterThanOrEqualTo(1)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMin", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(1)')
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(parsedJson.read("' + rootElement + '.valueWithMax", java.util.Collection.class)).hasSizeLessThanOrEqualTo(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()).isBetween(1, 3)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMinMax", java.util.Collection.class)).hasSizeBetween(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(parsedJson.read("' + rootElement + '.valueWithMinEmpty", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(0)')
test.contains('assertThat((Object) parsedJson.read("' + rootElement + '.valueWithMaxEmpty")).isInstanceOf(java.util.List.class)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMaxEmpty", java.util.Collection.class).size()).isLessThanOrEqualTo(0)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.valueWithMaxEmpty", java.util.Collection.class)).hasSizeLessThanOrEqualTo(0)')
!test.contains('cursor')
and:
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, blockBuilder.toString())
@@ -238,4 +238,58 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$'
}
@Issue('#217')
def "should use the flattened assertions when jsonpath contains [*] for [#methodBuilderName]"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
url 'person'
}
response {
status 200
body([
"phoneNumbers": [
number: "foo"
]
])
testMatchers {
jsonPath('$.phoneNumbers[*].number', byType {
minOccurrence(0)
maxOccurrence(4)
})
jsonPath('$.phoneNumbers[*].number', byType {
minOccurrence(0)
})
jsonPath('$.phoneNumbers[*].number', byType {
maxOccurrence(4)
})
}
}
}
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)).hasFlattenedSizeBetween(0, 4)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.phoneNumbers[*].number", java.util.Collection.class)).hasFlattenedSizeGreaterThanOrEqualTo(0)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.phoneNumbers[*].number", java.util.Collection.class)).hasFlattenedSizeLessThanOrEqualTo(4)')
!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) } | '$'
}
}