Add an @XmlTest test slice similar to @JsonTest - #51286
Open
basteez wants to merge 3 commits into
Open
Conversation
Checkpoint before re-deriving the assertion layer following adversarial review. Review found the XPath support is not namespace-aware, evaluates every expression as a single NODE, and that equality semantics are inverted relative to the JSON equivalents. This commit preserves the working slice and module wiring so the re-derivation is recoverable. This commit will be squashed before the pull request is opened. See spring-projectsgh-49872 Signed-off-by: Tiziano Basile <tiz.basile@gmail.com>
Add an @XmlTest test slice mirroring @jsontest, together with a JacksonXmlTester and an XML assertion stack, so XML serialization can be tested using the application's own XmlMapper configuration. See spring-projectsgh-49872 Signed-off-by: Tiziano Basile <tiz.basile@gmail.com>
Make negative assertions fail loudly when either document is unparseable, matching JsonContentAssert, rather than treating a parse failure as a difference and passing. Reject the reserved xml and xmlns prefixes in withNamespaces and carry the AssertJ representation onto the returned instance. Detect UTF-32 and BOM-less UTF-16 in XmlLoader, strip a byte order mark even when a charset is given, report malformed bytes instead of substituting replacement characters, and fail when a declaration declares an unreadable encoding. Parse with coalescing enabled for XPath so a node set sees the same text as a string evaluation, while leaving the comparison parser alone so a strict comparison still distinguishes a CDATA section from ordinary text. Also correct the documented limits of lenient and strict comparison, note the required XMLUnit version, and drop the deprecation from XmlContent.assertThat. See spring-projectsgh-49872 Signed-off-by: Tiziano Basile <tiz.basile@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an
@XmlTestslice alongside@JsonTest, plus aJacksonXmlTesterand the assertion support it needs, so XML serialization can be tested with the application's ownXmlMapperconfiguration.Closes gh-49872
What this adds
@XmlTestfollows the@JsonTestfile set one for one: the annotation,@AutoConfigureXml,@AutoConfigureXmlTesters,@ConditionalOnXmlTesters, a type exclude filter, a context bootstrapper and an auto-configuration that registers the tester field post processor. Component scanning is limited to@JacksonComponentbeans andJacksonModuleimplementations, matching the JSON slice.On the assertion side there is a new
org.springframework.boot.test.xmlpackage containingXmlContent,XmlContentAssert,AbstractXmlMarshalTesterandJacksonXmlTester.module/spring-boot-jacksoncontributesJacksonXmlTesterTestAutoConfigurationand the threeMETA-INF/springregistration files.Things worth your opinion
A few decisions here could reasonably have gone the other way, so I have called them out rather than burying them.
A parallel tester base was unavoidable.
AbstractJsonMarshalTester.write()returnsJsonContent<T>, andJsonContentis final, so covariant return cannot produce an XML content type. Generifying the existing base into something likeAbstractMarshalTester<T, C>would be behaviour preserving and arguably cleaner, but it changes a class that has been public since 1.4.0, so I did not touch it. Happy to go that way if you prefer. The read side reusesObjectContentandObjectContentAssertunchanged, since those turned out to be entirely format agnostic.Equality follows the JSON naming rather than XMLUnit's. XMLUnit talks about identical and similar comparisons. I went with
isEqualToXmlas the lenient one andisStrictlyEqualToXmlas the exact one, so thatisEqualToJsonandisEqualToXmlmean the same thing. Someone porting a@JsonTestwill rename mechanically, and silently getting stricter semantics seemed like the worse failure mode.isEqualTo(Object)is overridden to dispatch by source type the same wayJsonContentAssertdoes, otherwise it quietly falls through to raw string comparison.XPath is namespace aware. There is a
withNamespaces(Map)that returns a new assertion bound to those prefixes, shaped onXpathExpectationsHelper. Without it no expression can address a namespaced document, and unprefixed expressions must not match namespaced elements, which is the more dangerous half. Each method also evaluates the XPath result type it actually needs rather than always asking for a node, socount(...)works and an expression matching several nodes is rejected instead of silently asserting the first one.Two pieces of JSON named API are reused rather than duplicated. The XML testers return
org.springframework.boot.test.json.ObjectContent, andJacksonXmlTesterTestAutoConfigurationuses the existing publicJsonTesterFactoryBean, which is generic over the marshaller type and already shared by the Gson, Jsonb and Jackson 2 modules. It works, but it does mean a pure XML test imports from a JSON package. If you would rather those moved to a neutral package or type, that is much easier to do now than after 4.2.0 ships.Scope. This binds to Jackson XML only. There is no JAXB support and no Jackson 2 XML tester, and the
@XmlTestjavadoc says so plainly. I also left out aBasicXmlTester, soXmlTestersAutoConfigurationregisters only the bean post processor. Both felt like they should be your call rather than something I assumed, and if you want the name narrowed to something like@JacksonXmlTestI am glad to do that.Dependencies
No new managed dependencies.
xmlunit-coreis already on every Boot test classpath throughspring-boot-starter-test, socore/spring-boot-testjust declares it as optional.jackson-dataformat-xmlis added as optional there too, mirroring howmodule/spring-boot-jacksonalready declares it.One thing to note: the parser configuration uses
DocumentBuilderFactoryConfigurer.DefaultWithDTDParsing, which is only available from XMLUnit 2.12.0. That matches the version the BOM pins today, but it is an effective floor.A note on the assertion semantics
Negative assertions fail loudly when either document is unparseable, rather than treating a
parse failure as a difference and passing. That matches
JsonContentAssert, and it mattersbecause the alternative means a typo in a fixture makes the assertion green forever.
XmlLoaderdecides the encoding of an expected document from its byte order mark or its XMLdeclaration rather than assuming UTF-8, which keeps the expected side consistent with the
read side, where the prolog is normative.
Documentation
There is a new "Auto-configured XML Tests" section next to the JSON one, with Java and Kotlin samples. I have tried to keep every claim in it true of the code, in particular the limits of the lenient comparison: sibling elements that share a name are matched on name and text content, so ordering stops being insignificant once those siblings differ only in their child elements or only in their attributes.
Testing
:core:spring-boot-test:check,:core:spring-boot-test-autoconfigure:checkand:module:spring-boot-jackson:checkall pass, along with the documentation samples. There are tests incore/spring-boot-test-autoconfigurecovering the slice properties attribute and the@SpringBootTestplus@AutoConfigureXmlTesterspath, which is what actually pins thespring.test.xmltestersprefix, and integration tests inmodule/spring-boot-jacksoncovering the end to end behaviour including the case wherejackson-dataformat-xmlis absent.I also verified it from outside the build, as a downstream consumer of the published snapshot, covering the happy path, comparison semantics, XPath extraction, namespaces and the slice actually staying narrower than
@JsonTest.