Skip to content

XpathExpectationsHelper should support Hamcrest matching against typed objects [SPR-14722] #19287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Sep 15, 2016 · 7 comments
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Sep 15, 2016

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?
Pls. read the latest comment

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:

Backported to: Contributions Welcome

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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...

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Regarding JAXB2 and generic collections have you seen the Jaxb2CollectionHttpMessageConverter? Something along those lines perhaps.

@spring-projects-issues
Copy link
Collaborator Author

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'

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

I don't mean using the converter directly but doing something like what it does and iterate over a NodeList.

@spring-projects-issues spring-projects-issues added in: test Issues in the test module status: backported An issue that has been backported to maintenance branches type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.x Backlog milestone Jan 11, 2019
@rstoyanchev rstoyanchev removed the status: backported An issue that has been backported to maintenance branches label Jan 5, 2024
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 5, 2024

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);
}

@rstoyanchev rstoyanchev closed this as not planned Won't fix, can't repro, duplicate, stale Jan 5, 2024
@rstoyanchev rstoyanchev removed this from the 6.x Backlog milestone Jan 5, 2024
@sbrannen sbrannen added the status: declined A suggestion or change that we don't feel we should currently apply label Jan 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants