We are trying to implement contracts for list of objects which contains a list of basic objects (Integer) we just saw that having something like
body([
[
listNumbers: [
50, 100, 150, 200, 300, 1000
],
]
])
bodyMatchers {
jsonPath('$[*].listNumbers[*]', byRegex(positiveInt()))
jsonPath('$[*].listNumbers', byType {
minOccurrence(0)
})
}
is generating
assertThat((java.lang.Iterable) parsedJson.read("$[*].listNumbers[*]", java.util.Collection.class)).as("$[*].listNumbers[*]").allElementsMatch("([1-9]\\d*)");
assertThat((Object) parsedJson.read("$[*].listNumbers")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$[*].listNumbers", java.util.Collection.class)).as("$[*].listNumbers").hasFlattenedSizeGreaterThanOrEqualTo(0);
which fail for whatever minOcurrence greater than 0 since counter in the implementation is never update if we have an simple list of objects..
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;
}
May be I miss a call to the _size(Iterable iterable)_ function.
But if we just have
body(
[
listNumbers: [
50, 100, 150, 200, 300, 1000
],
]
)
bodyMatchers {
jsonPath('$.listNumbers[*]', byRegex(positiveInt()))
jsonPath('$.listNumbers', byType {
minOccurrence(0)
})
}
we got
assertThat((java.lang.Iterable) parsedJson.read("$.listNumbers[*]", java.util.Collection.class)).as("$.listNumbers[*]").allElementsMatch("([1-9]\\d*)");
assertThat((Object) parsedJson.read("$.listNumbers")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.listNumbers", java.util.Collection.class)).as("$.listNumbers").hasSizeGreaterThanOrEqualTo(0);
which works more better with values greater than 1, in fact it works.
So we are a bit lost about if the actual behaviour is the expected one or there is a bug but it's like a bit frustrating trying to understand why this behaviour.
Nowadays we found this is happening with Spring Cloud Versions 2.0.4.RELEASE and 2.1.1.RELEASE, so looks like is the expected way to be? is this is true, how we can achieve to get a contract as we want, have the same validation expresion for a return object and a list of objects of the same type.
Kind Regards.
Hello @kszosze Please provide example of bodies that this endpoint returns. If the json you return, looks like this:
[json]
{
"listNumbers": [
50,
100,
150,
200,
300,
1000
]
}
, the jsonPath $[*].listNumbers[*] is not matched to any element.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Of course not. because you are wrong with the example.
{
[
"listNumbers": [
50,
100,
150,
200,
300,
1000
]
]
}
This is the one which match the issue and the jsonpath expression you put.
Ok. Will have a look at it tomorrow.
Passing the json above causes ParseException to be thrown from Jayway JsonPath parser , so executing the test with it would cause it to fail before reaching the line in question.
Do you mean something like the json below, instead?
[json]
{
"objects": [
{
"listNumbers": [
50,
100,
150,
200,
300,
1000
]
}
]
}
If not, please provide a sample controller endpoint method code that can be used to replicate the problem or a correct json String that we can use to replicate the issue.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
I was using example from DSL groovy contracts in that case my example is correct, a list of elements which contains a property which is a list. If you want a plain JSON example
[
"listNumbers": [
50,
100,
150,
200,
300,
1000
]
]
this one should do the trick
@kszosze a json with only the excerpt you put above will not be parsed correctly either; please pass the entire plain json that your endpoint is returning, so that we can test with it.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Ok, let's gonna start from the start..
I'm creating contracts using groovy dsl not a json file.. so the code snippet I'm using is the right one which is wrongly parsed.
As you know the Spring Contracts project, convert the groovy definition into a Json stream with is parsed and verify. So is my believe that the problem is in the flattenedSize function I pasted before.
Hey @kszosze, can you please send what @OlgaMaciaszek asked you for? We need to see the json that you're endpoint returns in the test.
something like that,
[
{
"id": 45,
"listNumbers": [
10,
30,
50,
60
]
},
{
"id": 49,
"listNumbers": [
20,
40,
60,
80
]
},
{
"id": 50,
"listNumbers": [
5,
15,
25,
35
]
},
{
"id": 60,
"listNumbers": [
6,
26,
36,
46
]
}
]
Ok, thanks.
which fail for whatever minOcurrence greater than 0 since counter in the implementation is never update if we have an simple list of objects..
What's the stacktrace?
There is not a stacktrace. The test fail because the condition fail if you put something different than 0.
Ok I'll need a sample to replicate this. Could you create a small one please?
You mean a project that generate this JSON (no i have one to share right no) or a Contract in DSL as I already share from the start ( I can share a more complete one )
We need to replicate the issue you have. We need to have the DSL that generates the test, your plugin setup, the controller that returns the aforementioned JSON and the base class for your tests.
Well that gonna take a bit of time so I need to recreate it just for you. Actual problem came at work,and I cannot disclosure the code, as you understand. But I'll try to recreate it asap.
Great, that will be extremely helpful, thank you.
OK, I manage to adapt an existing project of mine in order to replicate the issue.. more or less.. here I'm using latest version in order to test contracts with collections of elements.
https://github.com/kszosze/rock_game
There are two contracts created for that case and both are failing:
Hello there, any feedback about?
Hi @kszosze thanks - will have a look.
Ok, I see which issue you mean now. Yes, looks like a bug. We'll provide a fix. Thanks.
Actually, we should not use hasFlattenedSizeGreaterThanOrEqualTo() here at all, since the jsonPath points to the stats element and not stats[*]. So actually, for the example code you have provided, we should verify that the number of stats occurrences is greater than 1, which indeed it is, as there are 3 stats occurrences in the produced json.
If you want to get minOccurrence for all the elements of the stats array, then I assume the right jsonPath would be stats[*] - granted, this also does not work properly at the moment. We'll be fxing this as well.
After further analyzing it, I don't think we should use hasFlattenedSize at all. It should be up to the user to point to the correct element, either list ($[*].stats) or all its elements ($[*].stats[*]), or particular named elements ($[*].stats[*].statA) to verify the min/max occurrence for.
Nevertheless, this functioning of the method has been documented:
** `byType()`: The value taken from the producer's response in the provided JSON path needs to be
of the same type as the type defined in the body of the response in the contract.
`byType` can take a closure, in which you can set `minOccurrence` and `maxOccurrence`. For the
request side, you should use the closure to assert size of the collection.
That way, you can assert the size of the flattened collection. To check the size of an
unflattened collection, use a custom method with the `byCommand(...)` `testMatcher`.
So in order to not cause backward incompatibility, I'm going to modify the counter.
Thank you!!!
Most helpful comment
Ok I'll need a sample to replicate this. Could you create a small one please?