當兩個 XML 和 SOAP 請求的主體相似時,如何用 XPath 配對
Matching XML and SOAP Requests with XPath When Two Bodies Look Alike
XML 或 SOAP 請求的主體相似時,WireMock 以 XPath 配對指定元素或屬性,比比對整段主體更可靠。為處理 SOAP 命名空間,需在配對器中明確設定命名空間,並檢查真正影響回應的值。用實際請求樣本測試相似請求,可確認配對準確,避免存根錯誤觸發。
Quick answer: When two XML or SOAP bodies look nearly identical, matching on the whole body is brittle, while XPath lets you target the specific element that distinguishes the requests. Define a matcher that reads a named node or attribute, handle namespaces explicitly, and assert on the value that actually changes the response. This is the reliable way to stub legacy SOAP services where the same envelope and operation carry different payloads.
Why whole-body matching fails for XML
XML is verbose and sensitive to detail that does not change meaning. A namespace prefix can be declared differently, whitespace can shift, or an element order can vary between clients, and an exact string comparison will treat those as different requests even though the logical call is the same. XPath solves the problem by reading the document as a tree and matching on the value of a particular node rather than the serialized text, so irrelevant formatting differences stop breaking the stub.
| Matching style | What it compares | Weakness |
|---|---|---|
| Exact body string | Serialized text | Breaks on whitespace or prefixes |
| XPath node value | A specific element | Requires namespace handling |
| XPath attribute | A named attribute | Must be present in the schema |
| Combined matchers | Several nodes together | More expressive, slightly more setup |
A SOAP body that needs distinguishing
Suppose two requests share the same envelope and operation, but the account type differs inside the body. That single element is what should decide which response the mock returns.
The distinguishing value is the AccountType node, so the matcher should read that node rather than compare the entire envelope. A second request with personal in the same node can then be routed to a different stub while everything else stays identical.
Defining the XPath matcher
WireMock supports XPath matching, and the expression namespaces the node you want. Because SOAP uses declared namespaces, the expression must account for them rather than treating the element as a bare name. The matcher can combine the node check with an equality condition so the stub only fires when the element holds the expected value.
{ "request": { "method": "POST", "urlPath": "/soap/accounts", "bodyPatterns": [ { "matchesXPath": "//acc:GetAccount/acc:AccountType[text()='business']", "xpathNamespaces": { "acc": "http://example.com/accounts", "soap": "http://schemas.xmlsoap.org/soap/envelope/" } } ] }, "response": { "status": 200, "bodyFileName": "business-account.xml", "headers": { "Content-Type": "text/xml" } } }
Namespace and prefix pitfalls
The most common XPath failure is ignoring namespaces. A document can use any prefix, including a default namespace, so an expression that assumes a bare element name will not match. Declare the namespace map and use the logical names, and test the expression against a real sample rather than assuming the path. Attribute matching is another useful variant: when the payload distinguishes records by an attribute such as a status code, read that attribute directly instead of adding a second element check.
Combining matchers for precision
One node is often enough, but a complex operation may need several conditions to avoid an accidental match. Combine the XPath for the operation with a second matcher for the ID or type, so a stub cannot fire on a different call that happens to share the same element value. The tradeoff is modest: more expressions mean slightly more setup, but they keep the stub deterministic as the number of legacy operations grows.
How an agent mishandles it
An agent that is asked to stub SOAP often starts with an exact body copy, which passes for the first request and breaks as soon as the client changes a prefix or adds whitespace. Ask it to identify the distinguishing node and express that as an XPath matcher with the namespace map. If the agent writes a path that ignores the namespace and then reports the mock as set up, the failure will only appear at runtime; require a test request against the stub to confirm the match.
Verifying the matcher against real samples
XPath expressions are easy to get subtly wrong, so always verify against a captured sample rather than assuming the path resolves. Send the representative request through the stub and confirm it selects the intended response, then send the near-identical request with a different node value and confirm it does not match. This pair proves the matcher is reading the distinguishing element and not firing too broadly on a shared envelope.
Keeping the sample files in the repository alongside the stub gives future changes a reliable reference and makes a broken namespace path fail in review rather than in the middle of a legacy integration. Once the expressions are verified, the mock can distinguish the two SOAP calls deterministically despite their near-identical bodies.
Frequently asked questions
Why not just match the full SOAP body?
Because XML formatting, prefixes, and whitespace can change without altering the meaning, so an exact string comparison treats equivalent calls as different. XPath reads the logical tree and targets the element that matters.
How do I handle the SOAP namespaces?
Declare the namespace map in the matcher and reference the elements by their logical names, since the prefix in the document can differ from the one in your expression.
Can I match on an attribute instead of an element?
Yes. XPath can read a named attribute, which is useful when the payload distinguishes records by an attribute rather than a child element.
When should I combine multiple XPath matchers?
When several operations share the same node value, add conditions for the operation and the ID together so a stub cannot fire on the wrong call.
This is part of the Powerduck vs WireMock: Spec-First vs Code-First Mocking — start there for the full comparison of mocking approaches.
來源:DEV Community · dev.to