6

So let's say I want to compare two DOMDocument objects. They have the same content but order and formatting might be off. For example, first one outputs this XML:

<responses>
    <response id="12">
        <foo>bar</foo>


 <lorem>ipsum</lorem>
           <sit>dolor</sit>

    </response></responses>

Other one outputs:

<responses>
<response id="12">

            <lorem>ipsum</lorem><sit>dolor</sit>
        <foo>bar</foo>
                            </response>
</responses>

As you can see, they contain the same XML structure but some elements might be in different order and formatting is completely random.

If I do:

$this->assertEquals();

The test will of course fail. I don't want to test just XML structure but also contents.

Any ideas?

hakre
  • 193,403
  • 52
  • 435
  • 836
Richard Knop
  • 81,041
  • 149
  • 392
  • 552

6 Answers6

4

This seems to have solved the problem:

https://phpunit.de/manual/current/en/appendixes.assertions.html#appendixes.assertions.assertXmlStringEqualsXmlString

Richard Knop
  • 81,041
  • 149
  • 392
  • 552
3

Which version of PHPUnit is this? I'm pretty sure recent versions all support DomDocument comparisons.

Short version: Use the $doc->preserveWhiteSpace setting to remove the whitespace, and then use $doc->C14N() to strip comments and get a string you can compare.


OK, here's a script you can play with, note that the EOD; lines cannot have any trailing or leading whitespace.

    $x1 = <<<EOD
<responses>
    <response id="12">
        <foo>bar</foo>

 <lorem>ipsum</lorem>
           <sit>dolor</sit>
        <!--This is a comment -->

    </response></responses>
EOD;

$x2 = <<<EOD
<responses>
<response id="12">

            <lorem>ipsum</lorem><sit>dolor</sit>
        <foo>bar</foo>
        <!--This is another comment -->
                            </response>
</responses>
EOD;

// The next block is part of the same file, I'm just making this formatting-break so that the StackOverflow syntax-highlighting system doesn't choke.

$USE_C14N = true; // Try false, just to see the difference.

$d1 = new DOMDocument(1.0);
$d2 = new DOMDocument(1.0);

$d1->preserveWhiteSpace = false;
$d2->preserveWhiteSpace = false;

$d1->formatOutput = false; // Only useful for "pretty" output with saveXML()
$d2->formatOutput = false; // Only useful for "pretty" output with saveXML()

$d1->loadXML($x1); // Must be done AFTER preserveWhiteSpace and formatOutput are set
$d2->loadXML($x2); // Must be done AFTER preserveWhiteSpace and formatOutput are set   

if($USE_C14N){
    $s1 = $d1->C14N(true, false);
    $s2 = $d2->C14N(true, false);
} else {
    $s1 = $d1->saveXML();
    $s2 = $d2->saveXML();
}

echo $s1 . "\n";
echo $s2 . "\n";

Output with $USE_C14N=true;

<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit></response></responses>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo></response></responses>

Output with $USE_C14N=false;

<?xml version="1.0"?>
<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit><!--This is a comment --></response></responses>

<?xml version="1.0"?>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo><!--This is another comment --></response></responses>

Note that $doc->C14N() might be slower, but I think it seems likely that stripping out comments is desirable. Note that all of this also assumes that whitespace in your XML isn't important, and there are probably some use-cases where that assumption isn't right...

Darien
  • 3,482
  • 19
  • 35
  • Not sure if it has changed, however the `$version` param for `DOMDocument` is string instead of float. Those using strict mode would get: `DOMDocument::__construct() expects parameter 1 to be string, float given` – Tom Raganowicz Apr 21 '20 at 07:15
1

I suggest you turn the XML into DOMDocuments and then use assertEquals with those. It's already supported by PHPUnit - However that might not cover all your needs already.

You can re-format the documents and re-load them as well, see PHP XML how to output nice format:

$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;

Another idea is to sort then the children by their tagname - no idea if that has been done before.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
0

You can use PHPUnit's assertXmlFileEqualsXmlFile(), assertXmlStringEqualsXmlFile() and assertXmlStringEqualsXmlString() functions; yet, they do not give informations on what's different, they only let the test fail with

Failed asserting that two DOM documents are equal.

So you might want to use PHP's XMLDiff PECL extension, or write your own recursive comparison function. If time matters, I'd recommend to not use DOM but SimpleXML instead because of the simpler API.

0

I've been playing with some of the notions presented here and figured I might as well post my end result. One of the things I wanted to be able to do was to compare the results of two nodes or two documents. (technically, this one can compare either or so long as the first child of a similar document is being compared to another)

Basically if I send in a DomDocument, it clones it using a $clone->loadXml($obj->saveXml) but if it's a node sent in, it does a $clone->importNode($obj); The order of the if's becomes important because DomDocument is also a instance of DomNode.

/**
 * @param \DOMDocument|\DOMNode $n1
 * @param \DOMDocument|\DOMNode $n2
 * @return bool
 * @throws \Exception for invalid data
 */
function compareNode($n1, $n2)
{
    $nd1 = new \DOMDocument('1.0', "UTF-8");
    if ($n1 instanceof \DOMDocument) {
        $nd1 = $n1->cloneNode(true);
        $nd1->preserveWhiteSpace = false;
        $nd1->formatOutput = false;
        $nd1->loadXML($n1->saveXML());
    } elseif ($n1 instanceof \DOMNode) {
        $nd1->preserveWhiteSpace = false;
        $nd1->formatOutput = false;
        $nd1->importNode($n1);
    } else {
        throw new \Exception(__METHOD__ . " node 1 is invalid");
    }

    $nd2 = new \DOMDocument('1.0', "UTF-8");
    if ($n2 instanceof \DOMDocument) {
        $nd2 = $n2->cloneNode(true);
        $nd2->preserveWhiteSpace = false;
        $nd2->formatOutput = false;
        $nd2->loadXML($n2->saveXML());
    } elseif ($n1 instanceof \DOMNode) {
        $nd2->preserveWhiteSpace = false;
        $nd2->formatOutput = false;
        $nd2->importNode($n2);
    } else {
        throw new \Exception(__METHOD__ . " node 2 is invalid");
    }

    return ($nd1->C14N(true, false) == $nd2->C14N(true, false));
}
Scott
  • 7,983
  • 2
  • 26
  • 41
0

Use the following assertion:

$this->assertXmlStringEqualsXmlString($expected, $actual);
Tahir
  • 483
  • 1
  • 4
  • 17