I have an API endpoint which, when called with GET, returns an array of JSON objects in the body, like this:
[
{"id": "321", "created": "2019-03-01", "updated": "2019-03-15"},
{"id": "123", "created": "2019-03-02", "updated": "2019-03-16"}
]
I would like to check the body with a Spring MockMvc test case.
The statement currently looks like this:
mockMvc.perform(get("/myapi/v1/goodstuff").
andExpect(status().isOk()).
andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
andExpect(jsonPath("$.*", isA(ArrayList.class))).
andExpect(jsonPath("$.*", hasSize(2))).
andExpect(jsonPath("$[0].id", is("321"))).
andExpect(jsonPath("$[0].created", is("2019-03-01"))).
andExpect(jsonPath("$[0].updated*", is("2019-03-15"))).
andExpect(jsonPath("$[1].id", is("1232"))).
andExpect(jsonPath("$[1].created", is("2019-03-02"))).
andExpect(jsonPath("$[1].updated*", is("2019-03-16")));
However, the implementation of my API doesn't guarantee the order of JSON object in the returned array.
Were this an array of strings, I would solve this via matcher generated by org.hamcrest.collection.IsIterableContainingInAnyOrder<T>.containsInAnyOrder.
But I cannot see any suitable matcher for my situation in their doc, nor any clue in the description of jsonPath method in Spring docs
From a quick search I didn't manage find anything related to my situation on SO, either, beyond a list of strings situation I described above.
Of course, I could convert JSON objects to strings.
But I'm wondering, could I solve this problem for a list of JSON objects, comparing each of the fields of each objects one-by-one (like shown in the code snippet above), but ignoring the order of objects in the collection?
Update: Zgurskyi has suggested a solution that helps with my original simplified example. However, with a real-life practical example there are 2 more inputs:
the number of fields is 10-20 instead of 3
not all of matchers are plain is, for instance:
(a bit closer to my original code)
mockMvc.perform(get("/myapi/v1/greatstuff").
andExpect(status().isOk()).
andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
andExpect(jsonPath("$.*", isA(ArrayList.class))).
andExpect(jsonPath("$.*", hasSize(2))).
andExpect(jsonPath("$[0].id", is("321"))).
andExpect(jsonPath("$[0].did", anything())).
andExpect(jsonPath("$[0].createdTs", startsWith("2019-03-01"))).
andExpect(jsonPath("$[0].updatedTs", startsWith("2019-03-15"))).
andExpect(jsonPath("$[0].name", equalToIgnoringCase("wat"))).
andExpect(jsonPath("$[0].stringValues", containsInAnyOrder("a","b","c"))).
andExpect(jsonPath("$[1].id", is("1232"))).
andExpect(jsonPath("$[1].did", anything())).
andExpect(jsonPath("$[1].createdTs", startsWith("2019-03-01"))).
andExpect(jsonPath("$[1].updatedTs", startsWith("2019-03-15"))).
andExpect(jsonPath("$[1].name", equalToIgnoringCase("taw"))).
andExpect(jsonPath("$[1].stringValues", containsInAnyOrder("d","e","f"))).
andReturn();
So far it seems that I can't do anything better than implementing my own matcher class.
Or...can I?
You can assert list items fields ignoring order:
.andExpect(jsonPath("$[*].id", containsInAnyOrder("321", "123")))
.andExpect(jsonPath("$[*].created", containsInAnyOrder("2019-03-01", "2019-03-02")))
.andExpect(jsonPath("$[*].updated", containsInAnyOrder("2019-03-15", "2019-03-16")))
Another approach would be to check that specific list items exist in response:
.andExpect(jsonPath("$.[?(#.id == 123 && #.created == \"2019-03-02\" && #.updated == \"2019-03-16\")]").exists())
.andExpect(jsonPath("$.[?(#.id == 321 && #.created == \"2019-03-01\" && #.updated == \"2019-03-15\")]").exists())
Additionally there is another way to assert the json without being strict about order using MockMvcResultMatchers
.andExpect(MockMvcResultMatchers.content().json(<json-here>, false))
By setting the strict=false, it can do a fussy search.
I think a better solution could be something like that:
.andExpect(jsonPath("$.violations", hasSize(3)))
.andExpect(jsonPath("$.violations", containsInAnyOrder(
Map.of("field", "name", "message", "must not be empty"),
Map.of("field", "email", "message", "must not be empty"),
Map.of("field", "birthdate", "message", "must not be null")
)
))
It worked for me, but I have to be honest, I don't like to use Map instead a domain type, like Violation, Tuple, Category etc. Unfortunately, I could not make it work with a type different than Map.
Related
I am stuck in a problem where I have to filter json data from a String which is a combination of json string and normal text.
sample: This message has all your required detail { "name" : "xyz","age": "21","place" :"sdf", "number": "7689"} check in this page you will get the details.
I need to extract the json object from given string.
Result expected is only : { "name" : "xyz","age": "21","place" :"sdf", "number": "7689"}.
Is there any clean way of doing this in Java.
One way to solve this is to remove non-json string and extract json object.
But that is a bad approach in my view.
If there are no other JSON-like parts, you can just extract the part between the first { and last }, including both ends:
int start=str.indexOf('{');
int end=str.lastIndexOf('}');
String json=str.substring(start,end+1);
Then of course you may want to check if both start and end are non-negative (so the characters are actually present), if there is a possibility that the string does not contain anything for you.
Also note that JSON can be an array too, so you can try checking if a pair of first [ and last ] lies outside of the {...}, but then at the end single values are valid JSON too (like true, false, 1, etc.). This is not really a happy task to write properly, thinking of everything.
I am trying to write a JUnit test, which checks the value of received JSON. I access these values in the JSON using jsonPath. I want to check if a value is true. For simple jsonPaths, it works for me, but when I write more complex jsonPath query, it does not and I am getting this Assertion Error:
Expected: is <true>
but: was <[true]>
My JSON:
{...
,"trips":
[
{...
,"employee":{"name":"Ordinary Joe","login":"joe","contractor":true}
,...
},
...
],
...
}
Problematic assertion
.andExpect(jsonPath("$.trips[?(#.employee.login=='joe')].employee.contractor", is(true)));
What I've tried
I tried to match the value also with new Boolean(true), boolean[] array with one true value and after examination of is() matcher also with String.valueOf(true) however it also did not match.
My question
How should I correctly match this true value? What exactly these [] braces mean in the test output?
In the further investigation, I actually found that jsonPath somehow converts the obtained true value to a JsonArray with one item and therefore it is not possible to match it with is(true) matcher. I am not sure why JsonPath does so.
I found the following working workaround using hasItem matcher instead:
.andExpect(jsonPath("$.trips[?(#.employee.login=='joe')].employee.contractor", hasItem(true)))
.andExpect(jsonPath("$.trips[?(#.employee.login=='joe')].employee.contractor", not(hasItem(false))))
Please consider a MongoDB collection with the following document:
"_id": "clientsInfo"
"data": {
"clientsList" : [
{
"name" : "Mike",
"country" : "USA"
},
...
]
}
After setting the DataSet and defining the Query like this...
{
collectionName:'projectA',
findQuery: {
'_id':'clientsInfo',
},
findFields: {
'_id':0,
'data.clientsList':1
},
}
...I am able to display the first item of the fetched array (java.util.List type) in JasperSoft Studio inside a Text Field using the following expression:
$F{data.clientsList}.get(0)
But, considering that I would like to exhibit the whole data in a Name/Country Table...
Question1: How could I access any of the dictionary fields? Trying get method I obtain The method get(String) is undefined for the type Object. error. However, knowing that the object is an instance of com.mongodb.BasicDBObject it should have that method inherited (See doc).
I have also tried to cast object to org.json.JSONObject but then I get net.sf.jasperreports.engine.fill.JRExpressionEvalException: Error evaluating expression for source text: (JSONObject)$F{data.clientsList}.get(0) error.
Question2: Let's suppose we have already solved first question... How can I iterate the list to access not only the first item but all of them according to the array length? Is it possible to use for-loop sentence inside the JasperSoft Expression Editor? (if-then-else seems to be available)
Thanks in advance, Any clue that point me in the right direction will be appreciated.
Just in case someone was in the same situation as I was, I must say this whole approach was wrong.
It's not about making a simple query which returns big block of complex data formatted as an object or list of objects and then manipulate it with JasperSoft Studio. Instead, what I had to do was design a more elaborated query which returns the simple fields I wanted to use straightforward. How to do this? By using Aggregation Framework.
So, by changing this...
{
collectionName:'projectA',
findQuery: {
'_id':'clientsInfo',
},
findFields: {
'_id':0,
'data.clientsList':1
},
}
...for this...
{
runCommand: {
aggregate : 'projectA',
pipeline : [
{'$match': {'_id':'clientsInfo'}},
{'$project': {'data.clientsList': 1}},
{'$unwind': '$data'},
{'$unwind': '$data.clientsList'}
]
}
}
...is how I get name and country fields in order to use them in Text Fields, Tables, ...etc.
Let's say that we have the following json response:
{
"data":
[
{
"id": 1,
"name": "Pablo"
},
{
"id": 2,
"name": "Ernesto"
}
]
...
}
Where the data list could consist of many more objects. If I where to verify that no name field is set to null, what would be the Rest Assured way of doing this?
Now I'm using:
from(response.asString()).get("data");
to get a list of HashMaps, and then moving on from there for each entry. But I guess there is some other way that is more efficient?
Edit/Clarification: I am wondering if there is a way to do this without the creation of a list of maps?
i guess this is the best we can do
List<HashMap> data = from(response.asString()).get("data");
for(HashMap map: data){
if(map.get("name") == null){
// null name found
}
}
// No null name found
Ok, so perhaps I should have read the docs a bit more carefully, the easiest (and intended) way to retrieve e.g. all name values (as in my example) is to use JsonPath like so:
List<Object> names = from(response.asString()).get("data.name");
So there are no magical utility methods within the Rest-Assured library to do this except using JsonPath.from().get() and then to go from there.
When Parsing JSON I normally just constuct an object and use the gsonlibrary to parse my String into that object.
However, I now find myself with a rather complex response which consists of many elements each with sub elements of objects and arrays and arrays of objects. It looks something like this...
{
"type": "thetype",
"object":{
"text": "texthere",
"moretext": "more here"
},
...,
...,
...,
...,
"fieldIwant": [
{
"object":"object!"
},
....
....
{
"object":"object!"
},
]
}
The thing is, I'm only really interested in fieldIwantand nothing else. Is there not a way in Java for me to just extract that field and work with it alone and not all this other dead weight I do not need?
According to this http://sites.google.com/site/gson/gson-design-document it looks like gson does this for you by default.
When you are deserializing a Json string into an object of desired type, you can either navigate the tree of the input, or the type tree of the desired type. Gson uses the latter approach of navigating the type of the target object. This keeps you in tight control of instantiating only the type of objects that you are expecting (essentially validating the input against the expected "schema"). By doing this, you also ignore any extra fields that the Json input has but were not expected.
In other words, it doesn't deserialize any of the fields you don't need. You should be good to go.
You can use the low level JsonParser API
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
yourArray = new Gson().fromJson(jsonObject.get("fieldIwant"), yourArrayType);
Alternatively you can create an ExclusionStrategy to use with a GsonBuilder