Manuel Jordan opened SPR-14722 and commented
I am working with:
- Spring Framework
4.3.2
About Spring MVC Test
for Json I have the following:
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.id").value(is("100")))
.andExpect(jsonPath("$.id").value(is(persona.getId())))
.andExpect(jsonPath("$.nombre").exists())
.andExpect(jsonPath("$.nombre").value(is("Jesús Você")))
.andExpect(jsonPath("$.nombre").value(is(persona.getNombre())))
.andExpect(jsonPath("$.apellido").exists())
.andExpect(jsonPath("$.apellido").value(is("Mão Nuñez")))
.andExpect(jsonPath("$.apellido").value(is(persona.getApellido())))
....
now consider a more complex object, either more fields or relation with other objects. With the following I have a considerable reduction of code
.andExpect(jsonPath("$", is(persona)))
.andExpect(jsonPath("$").value(is(persona)))
Same thought for collections of data returned in JSON format. Here jsonPath
works in peace with Hamcrest
The problem is with XML. I have the following:
.andExpect(xpath("persona/*").nodeCount(is(4)))
.andExpect(xpath("persona/id").exists())
.andExpect(xpath("persona/id").string(is("100")))
.andExpect(xpath("persona/id").string(is(persona.getId())))
.andExpect(xpath("persona/nombre").exists())
.andExpect(xpath("persona/nombre").string(is("Jesús Você")))
.andExpect(xpath("persona/nombre").string(is(persona.getNombre())))
.andExpect(xpath("persona/apellido").exists())
.andExpect(xpath("persona/apellido").string(is("Mão Nuñez")))
.andExpect(xpath("persona/apellido").string(is(persona.getApellido())))
The code works or pass, but again consider a more complex object, either more fields or relation with other objects. Sadly xpath
does not work with Hamcrest
. So I am not able to do a reduction of code. Same problem for collections of data returned in XML format.
This problem has been discussed in some way in:
SPR-13687 - Why MockMvcResultMatchers has not a xpath method with org.hamcrest.Matcher
Considering that Spring Framework 5 is coming, perhaps would have sense in re-consider in apply this improvement?
Thanks by your understanding.
Affects: 4.3 GA, 4.3.1, 4.3.2, 5.0 M1
Issue Links:
- #18262 Why MockMvcResultMatchers has not a xpath method with org.hamcrest.Matcher
Backported to: Contributions Welcome
Comment From: spring-projects-issues
Rossen Stoyanchev commented
XPath returns basic data types as well as org.w3c.dom.Node
/ org.w3c.dom.NodeList
. We could experiment turn that into a DOMSource
and use Jaxb2Marshaller
to unmarshal it.
Comment From: spring-projects-issues
Manuel Jordan commented
Sounds great
About:
We could experiment turn that into a DOMSource and use Jaxb2Marshaller to unmarshal it
I had the problem with JAXB2 that it does not support generic collections.
I must use Jackson for XML instead, the 'drawback' is that once used Jackson through a mandatory special module required for xml, the Jaxb2 annotations are ignored.
I am sharing this because because Spring 5 should work with Java 9, and I don't if the API for Jaxb2 would be changed or Jaxb 3 would be created...
Comment From: spring-projects-issues
Rossen Stoyanchev commented
Regarding JAXB2 and generic collections have you seen the Jaxb2CollectionHttpMessageConverter
? Something along those lines perhaps.
Comment From: spring-projects-issues
Manuel Jordan commented
The special module is jackson-dataformat-xml
, it from FasterXML/jackson-dataformat-xml
Now the problem is the following bug:
XML Empty tag to Empty string in the object during xml deserialization
Practically reflected in:
Spring Rest & Jackson: Empty values are transformed in Null values in XML transformation
How you can see Jaxb2 and Jackson have each one its own 'situations'
Comment From: spring-projects-issues
Manuel Jordan commented
About Jaxb2CollectionHttpMessageConverter
I think I had tested and did not work... the reason? I can't remember
Not sure If I have reported through JIRA or Stackoverflow (I think the former) this situation and from either of them Jaxb2CollectionHttpMessageConverter
was suggested, but did not work... the solution was Jackson with that special module.
Comment From: spring-projects-issues
Rossen Stoyanchev commented
I don't mean using the converter directly but doing something like what it does and iterate over a NodeList.
Comment From: rstoyanchev
Revisiting this issue, there are some things I overlooked. In order to compare to a higher level object, you need to specify a target class, so it should be this at least:
.andExpect(jsonPath("$", is(persona), Persona.class))
Even then, unless JSONPath is configured with a JsonProvider
, the JsonSmartMappingProvider
used by default only decodes to maps of objects. This was raised in #31423 and #27486 recently. We do have an improvement planned with #31651 for JsonPathExpectationsHelper
to expose JSONPath for configuration. However, the general challenge is that with static methods for ResultMatcher
implementations, there is no way to have JSON or XML library configured during setup, and the config would have to be passed in every time or configured statically, neither of which is a good solution.
This is why I don't think we can do much more here.
That said, I do actually have an alternative to suggest. If the goal is to compare higher level objects, you can use WebTestClient
. Unlike MockMvc that does server side testing, WebTestClient
is an actual test client that does encoding and decoding to higher level objects, and it can be used with any server, including MockMvc through MockMvcWebTestClient
. You can use it to do something like:
@Test
void xml() {
MockMvcWebTestClient.bindToController(new PersonController()).build()
.get()
.uri("/person/Lee")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_XML)
.expectBody(Person.class)
.isEqualTo(person);
}