Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHPUnit: assert two arrays are equal, but order of elements not important

People also ask

Which assertion would you use to test if two arrays contain the same elements?

24, $this->assertEquals asserts the array contains the same keys and values, disregarding in what order.

What is assertion in PHPUnit?

The assertion methods are declared static and can be invoked from any context using PHPUnit\Framework\Assert::assertTrue() , for instance, or using $this->assertTrue() or self::assertTrue() , for instance, in a class that extends PHPUnit\Framework\TestCase .

How to find common elements in two arrays in PHP?

The array_intersect() function compares the values of two (or more) arrays, and returns the matches. This function compares the values of two or more arrays, and return an array that contains the entries from array1 that are present in array2, array3, etc.


You can use assertEqualsCanonicalizing method which was added in PHPUnit 7.5. If you compare the arrays using this method, these arrays will be sorted by PHPUnit arrays comparator itself.

Code example:

class ArraysTest extends \PHPUnit\Framework\TestCase
{
    public function testEquality()
    {
        $obj1 = $this->getObject(1);
        $obj2 = $this->getObject(2);
        $obj3 = $this->getObject(3);

        $array1 = [$obj1, $obj2, $obj3];
        $array2 = [$obj2, $obj1, $obj3];

        // Pass
        $this->assertEqualsCanonicalizing($array1, $array2);

        // Fail
        $this->assertEquals($array1, $array2);
    }

    private function getObject($value)
    {
        $result = new \stdClass();
        $result->property = $value;
        return $result;
    }
}

In older versions of PHPUnit you can use an undocumented param $canonicalize of assertEquals method. If you pass $canonicalize = true, you will get the same effect:

class ArraysTest extends PHPUnit_Framework_TestCase
{
    public function testEquality()
    {
        $obj1 = $this->getObject(1);
        $obj2 = $this->getObject(2);
        $obj3 = $this->getObject(3);

        $array1 = [$obj1, $obj2, $obj3];
        $array2 = [$obj2, $obj1, $obj3];

        // Pass
        $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);

        // Fail
        $this->assertEquals($array1, $array2, "Default behaviour");
    }

    private function getObject($value)
    {
        $result = new stdclass();
        $result->property = $value;
        return $result;
    }
}

Arrays comparator source code at latest version of PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46


The cleanest way to do this would be to extend phpunit with a new assertion method. But here's an idea for a simpler way for now. Untested code, please verify:

Somewhere in your app:

 /**
 * Determine if two associative arrays are similar
 *
 * Both arrays must have the same indexes with identical values
 * without respect to key ordering 
 * 
 * @param array $a
 * @param array $b
 * @return bool
 */
function arrays_are_similar($a, $b) {
  // if the indexes don't match, return immediately
  if (count(array_diff_assoc($a, $b))) {
    return false;
  }
  // we know that the indexes, but maybe not values, match.
  // compare the values between the two arrays
  foreach($a as $k => $v) {
    if ($v !== $b[$k]) {
      return false;
    }
  }
  // we have identical indexes, and no unequal values
  return true;
}

In your test:

$this->assertTrue(arrays_are_similar($foo, $bar));

My problem was that I had 2 arrays (array keys are not relevant for me, just the values).

For example I wanted to test if

$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");

had the same content (order not relevant for me) as

$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");

So I have used array_diff.

Final result was (if the arrays are equal, the difference will result in an empty array). Please note that the difference is computed both ways (Thanks @beret, @GordonM)

$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));

For a more detailed error message (while debugging), you can also test like this (thanks @DenilsonSá):

$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));

Old version with bugs inside:

$this->assertEmpty(array_diff($array2, $array1));


One other possibility:

  1. Sort both arrays
  2. Convert them to a string
  3. Assert both strings are equal

$arr = array(23, 42, 108);
$exp = array(42, 23, 108);

sort($arr);
sort($exp);

$this->assertEquals(json_encode($exp), json_encode($arr));

Simple helper method

protected function assertEqualsArrays($expected, $actual, $message) {
    $this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}

Or if you need more debug info when arrays are not equal

protected function assertEqualsArrays($expected, $actual, $message) {
    sort($expected);
    sort($actual);

    $this->assertEquals($expected, $actual, $message);
}