Reputation: 1345
So I have the following input, expected output and actual output xml:
input.xml
<Request>
<EmailSubjectLine>Main Contact & No Reported To</EmailSubjectLine>
<ProductRq>
<Signon>
<ClientDt>1/6/2017 11:25:45 AM</ClientDt>
<CustLangPref>en-US</CustLangPref>
</Signon>
<SvcRq>
<RqUID>xxxxxxxx-2802-xxxx-xxxx-bf8361xxxxxx</RqUID>
<NotificationRq>
<TransactionRequestDt>2017-01-06</TransactionRequestDt>
<Currency>USD</Currency>
</NotificationRq>
</SvcRq>
</ProductRq>
<!-- rest of input -->
</Request>
expected-output.xml
<ProductRq xmlns="http://test.org/standards/intake">
<Audit>
<TransID>Test</TransID>
</Audit>
<Signon>
<ClientDt>1/6/2017 11:25:45 AM</ClientDt>
<CustLangPref>en-US</CustLangPref>
</Signon>
<SvcRq>
<RqUID>xxxxxxxx-2802-xxxx-xxxx-bf8361xxxxxx</RqUID>
<NotificationRq>
<RqUID>Test</RqUID>
<TransactionRequestDt>2017-01-06</TransactionRequestDt>
<Currency>USD</Currency>
</NotificationRq>
</SvcRq>
<!-- rest of expected-output -->
</ProductRq>
actual-output.xml
<ProductRq xmlns="http://test.org/standards/intake">
<Audit>
<TransID>123534Abwe-asdcv-1258qw-asd</TransID>
</Audit>
<Signon>
<ClientDt>1/6/2017 11:25:45 AM</ClientDt>
<CustLangPref>en-US</CustLangPref>
</Signon>
<SvcRq>
<RqUID>xxxxxxxx-2802-xxxx-xxxx-bf8361xxxxxx</RqUID>
<NotificationRq>
<RqUID>CG-17Dawe-12354-Hw35Sf</RqUID>
<TransactionRequestDt>2017-01-06</TransactionRequestDt>
<Currency>USD</Currency>
</NotificationRq>
</SvcRq>
<!-- rest of actual-output -->
</ProductRq>
I'm comparing them with the following Diff set up:
MyTest.java
Diff diff = DiffBuilder
.compare(xmlExpectedOutput)
.withTest(xmlOutput)
.normalizeWhitespace()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.conditionalBuilder()
.whenElementIsNamed("Audit")
.thenUse(ElementSelectors.byXPath("./TransID", ElementSelectors.byName))
.whenElementIsNamed("NotificationRq")
.thenUse(ElementSelectors.byXPath("./RqUID", ElementSelectors.byName))
.elseUse(ElementSelectors.byNameAndText)
.build()
))
.checkForSimilar()
.build();
I get the following differences when I run the above input and compare with expected-output.xml:
[Expected child '{http://test.org/standards/intake}RqUID' but was 'null' - comparing <RqUID...> at /ProductRq[1]/SvcRq[1]/NotificationRq[1]/RqUID[1] to <NULL> (DIFFERENT), Expected child 'null' but was '{http://test.org/standards/intake}RqUID' - comparing <NULL> to <RqUID...> at /ProductRq[1]/SvcRq[1]/NotificationRq[1]/RqUID[1] (DIFFERENT)]
I don't get why my Element selector wouldn't work, am I using it incorrectly? My aim is whenever TransmissionId or NotificationRq/RqUID are found, to match them with the expected output versions by name only, otherwise use name and text for other elements as these elements contain unique generated ids that change every test run and can't be predicted(with a view to creating a more complex selector later, e.g. to compare ProductRq via name and attribute as a namespace is added to this). Is there something I'm missing, and am I able to combine the 2 XPath selectors together rather than several when/then lines and the default case?
Note: the xml is transformed via xslt. The namespace on PRoductRq is not there on the source document; the source is copied, the namespace added to ProductRq and then sent for output along with some element removals/modifications/additions
Upvotes: 1
Views: 851
Reputation:
XMLUnit says the RqUID
elements inside the NotificationRq
wouldn't match and of course they are different.
.whenElementIsNamed("NotificationRq")
.thenUse(ElementSelectors.byXPath("./RqUID", ElementSelectors.byName))
means: when XMLUnit tries to find a partner for an NotificationRq
element then it has to search for an NotificationRq
that has an RqUID
child - and only use the RqUID
element.
It doesn't set up any rules for any other element, in particular RqUID
itself. For RqUID
elements the default rules apply and
.elseUse(ElementSelectors.byNameAndText)
says: XMLUnit only accepts two elements as pairs if their names and the nested text match. Which is not the case for the RqUID
elements in question.
Your whole ElementSelector
says
Audit
s if they have TransID
children of arbitrary content.NotificationRq
s if they have RqUID
of arbitrary content.which doesn't fit your example. Looking at your XML you probably wanted
TransId
children of Audit
sRqUID
children of NotificationRq
There is no built-in predicate for "element named foo
if it is a child of an element named bar
", it could be something like
Predicate<Element> transIdInAudit = e -> {
if (e == null || e.getParentNode() == null) {
return false;
}
return "TransID".equals(e.getLocalName()) && "Audit".equals(e.getParentNode().getLocalName());
};
which you likely want to make generalizable :-)
With that you'd use
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.conditionalBuilder()
.when(transIdInAudit)
.thenUse(ElementSelectors.byName)
.when(rqUIDInNotificationRq) // similar to transIdInAudit
.thenUse(ElementSelectors.byName)
.elseUse(ElementSelectors.byNameAndText)
.build())
Maybe you really want to match SvcRq
if they have matching RqUID
, maybe not. If so you'd use the structure you currently use for NotificationRq
.
This in itself will not be enough to ignore the nested text of the matched TransId
and RqUID
elements, it will only ensure XMLUnit will pick the nodes you want it to use. For the nested text you'll need a DifferenceEvaluator
.
Given that you are using ElementSelectors.byNameAndText
by default, you know the nested texts are the same for all matched nodes except for the two specific elements where you want to ignore the content. So a DifferenceEvaluator
like
DifferenceEvaluators.chain(DifferenceEvaluators.Default,
DifferenceEvaluators.downgradeDifferencesToEqual(ComparisonType.TEXT_VALUE))
should work.
Upvotes: 2