hamcrest/hamcrest-php · error · InvalidArgumentException
Must pass a valid XML document
Error message
Must pass a valid XML document
What it means
HasXPath::createDocument() detects a leading XML declaration (<?xml) and then attempts loadXML(); when parsing fails (invalid XML) it throws InvalidArgumentException with 'Must pass a valid XML document'. loadXML errors are suppressed with @ so the exception is the only failure signal.
Solutions
- Validate the XML string with a standalone loadXML check before matching
- Fix the XML source so it is well-formed (escape &, close tags, valid declaration)
- If the content is actually HTML, remove the <?xml declaration so the HTML branch is used
- Use a validator (DOMDocument::loadXML in try/catch or xml_parse) on the producer side
Example fix
// before
assertThat($badXml, hasXPath('//item'));
// after
$doc = new DOMDocument();
libxml_use_internal_errors(true);
if (!@$doc->loadXML($badXml)) { throw new RuntimeException('XML malformed: '.libxml_get_last_error()->message); }
assertThat($badXml, hasXPath('//item')); Defensive patterns
Strategy: validation
Validate before calling
function isValidXml(string $text): bool {
if (!preg_match('/^\s*<\?xml/', $text)) return false;
$d = new DOMDocument();
libxml_use_internal_errors(true);
$ok = @$d->loadXML($text);
libxml_clear_errors();
return $ok;
} Try / catch
try {
assertThat($xml, hasXPath('//item'));
} catch (\InvalidArgumentException $e) {
if (strpos($e->getMessage(), 'valid XML document') === false) throw $e;
fail('Malformed XML received: '.substr($xml, 0, 200));
} Prevention
- Validate fixtures/responses with loadXML once at setup
- Set libxml_use_internal_errors(true) and log libxml_get_errors() in producers
- Escape & as & and verify character encoding declarations
- Snapshot-test API XML output to catch regressions
When it happens
Trigger: Passing text starting with <?xml that is not well-formed XML to hasXPath(), e.g. truncated documents, unescaped ampersands, mismatched tags.
Common situations: API responses truncated mid-document; invalid XML fed from config files or fixtures; XML containing entities not declared.
Related errors
- Must pass a valid HTML or XHTML document
- Must pass an object, array, or class name
- assertThat() requires one to three arguments
- Each argument or element must be a Hamcrest matcher
AI-assisted analysis of hamcrest/hamcrest-php@aa726aeff9 (2026-09-15).
Data as JSON: /api/errors/fa06b3bbb2f98b65.
Report an issue: GitHub.
Appendix: source
Thrown at hamcrest/Hamcrest/Xml/HasXPath.php:81
} else {
return $this->matchesExpression($result, $mismatchDescription);
}
}
/**
* Creates and returns a <code>DOMDocument</code> from the given
* XML or HTML string.
*
* @param string $text
* @return \DOMDocument built from <code>$text</code>
* @throws \InvalidArgumentException if the document is not valid
*/
protected function createDocument($text)
{
$document = new \DOMDocument();
if (preg_match('/^\s*<\?xml/', $text)) {
if (!@$document->loadXML($text)) {
throw new \InvalidArgumentException('Must pass a valid XML document');
}
} else {
if (!@$document->loadHTML($text)) {
throw new \InvalidArgumentException('Must pass a valid HTML or XHTML document');
}
}
return $document;
}
/**
* Applies the configured XPath to the DOM node and returns either
* the result if it's an expression or the node list if it's a query.
*
* @param \DOMNode $node context from which to issue query
* @return mixed result of expression or DOMNodeList from query
*/
protected function evaluate(\DOMNode $node)View on GitHub (pinned to aa726aeff9)