Skip to content

Add an @XmlTest test slice similar to @JsonTest - #51286

Open
basteez wants to merge 3 commits into
spring-projects:mainfrom
basteez:gh-49872-xml-test
Open

Add an @XmlTest test slice similar to @JsonTest#51286
basteez wants to merge 3 commits into
spring-projects:mainfrom
basteez:gh-49872-xml-test

Conversation

@basteez

@basteez basteez commented Aug 5, 2026

Copy link
Copy Markdown

Adds an @XmlTest slice alongside @JsonTest, plus a JacksonXmlTester and the assertion support it needs, so XML serialization can be tested with the application's own XmlMapper configuration.

Closes gh-49872

What this adds

@XmlTest follows the @JsonTest file 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 @JacksonComponent beans and JacksonModule implementations, matching the JSON slice.

On the assertion side there is a new org.springframework.boot.test.xml package containing XmlContent, XmlContentAssert, AbstractXmlMarshalTester and JacksonXmlTester. module/spring-boot-jackson contributes JacksonXmlTesterTestAutoConfiguration and the three META-INF/spring registration 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() returns JsonContent<T>, and JsonContent is final, so covariant return cannot produce an XML content type. Generifying the existing base into something like AbstractMarshalTester<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 reuses ObjectContent and ObjectContentAssert unchanged, 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 isEqualToXml as the lenient one and isStrictlyEqualToXml as the exact one, so that isEqualToJson and isEqualToXml mean the same thing. Someone porting a @JsonTest will rename mechanically, and silently getting stricter semantics seemed like the worse failure mode. isEqualTo(Object) is overridden to dispatch by source type the same way JsonContentAssert does, 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 on XpathExpectationsHelper. 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, so count(...) 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, and JacksonXmlTesterTestAutoConfiguration uses the existing public JsonTesterFactoryBean, 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 @XmlTest javadoc says so plainly. I also left out a BasicXmlTester, so XmlTestersAutoConfiguration registers 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 @JacksonXmlTest I am glad to do that.

Dependencies

No new managed dependencies. xmlunit-core is already on every Boot test classpath through spring-boot-starter-test, so core/spring-boot-test just declares it as optional. jackson-dataformat-xml is added as optional there too, mirroring how module/spring-boot-jackson already 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 matters
because the alternative means a typo in a fixture makes the assertion green forever.

XmlLoader decides the encoding of an expected document from its byte order mark or its XML
declaration 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:check and :module:spring-boot-jackson:check all pass, along with the documentation samples. There are tests in core/spring-boot-test-autoconfigure covering the slice properties attribute and the @SpringBootTest plus @AutoConfigureXmlTesters path, which is what actually pins the spring.test.xmltesters prefix, and integration tests in module/spring-boot-jackson covering the end to end behaviour including the case where jackson-dataformat-xml is 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.

basteez added 3 commits August 2, 2026 12:26
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>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 5, 2026
@philwebb philwebb changed the title Gh 49872 xml test Add an @XmlTest test slice similar to @JsonTest Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an @XmlTest test slice similar to @JsonTest

2 participants