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

PhpUnit TestingPhpunitAssert

Php Problem Overview


What is a good way to assert that two arrays of objects are equal, when the order of the elements in the array is unimportant, or even subject to change?

Php Solutions


Solution 1 - Php

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

Solution 2 - Php

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));

Solution 3 - Php

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));

Solution 4 - Php

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));

Solution 5 - Php

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);
}

Solution 6 - Php

If the array is sortable, I would sort them both before checking equality. If not, I would convert them to sets of some sort and compare those.

Solution 7 - Php

If the keys are the same but out of order this should solve it.

You just have to get the keys in the same order and compare the results.

 /**
 * Assert Array structures are the same
 *
 * @param array       $expected Expected Array
 * @param array       $actual   Actual Array
 * @param string|null $msg      Message to output on failure
 *
 * @return bool
 */
public function assertArrayStructure($expected, $actual, $msg = '') {
    ksort($expected);
    ksort($actual);
    $this->assertSame($expected, $actual, $msg);
}

Solution 8 - Php

Even though you do not care about the order, it might be easier to take that into account:

Try:

asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);

Solution 9 - Php

Using array_diff():

$a1 = array(1, 2, 3);
$a2 = array(3, 2, 1);

// error when arrays don't have the same elements (order doesn't matter):
$this->assertEquals(0, count(array_diff($a1, $a2)) + count(array_diff($a2, $a1)));

Or with 2 asserts (easier to read):

// error when arrays don't have the same elements (order doesn't matter):
$this->assertEquals(0, count(array_diff($a1, $a2)));
$this->assertEquals(0, count(array_diff($a2, $a1)));

Solution 10 - Php

We use the following wrapper method in our Tests:

/**
 * Assert that two arrays are equal. This helper method will sort the two arrays before comparing them if
 * necessary. This only works for one-dimensional arrays, if you need multi-dimension support, you will
 * have to iterate through the dimensions yourself.
 * @param array $expected the expected array
 * @param array $actual the actual array
 * @param bool $regard_order whether or not array elements may appear in any order, default is false
 * @param bool $check_keys whether or not to check the keys in an associative array
 */
protected function assertArraysEqual(array $expected, array $actual, $regard_order = false, $check_keys = true) {
	// check length first
	$this->assertEquals(count($expected), count($actual), 'Failed to assert that two arrays have the same length.');

	// sort arrays if order is irrelevant
	if (!$regard_order) {
		if ($check_keys) {
			$this->assertTrue(ksort($expected), 'Failed to sort array.');
			$this->assertTrue(ksort($actual), 'Failed to sort array.');
		} else {
			$this->assertTrue(sort($expected), 'Failed to sort array.');
			$this->assertTrue(sort($actual), 'Failed to sort array.');
		}
	}

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

Solution 11 - Php

The given solutions didn't do the job for me because I wanted to be able to handle multi-dimensional array and to have a clear message of what is different between the two arrays.

Here is my function

public function assertArrayEquals($array1, $array2, $rootPath = array())
{
	foreach ($array1 as $key => $value)
	{
		$this->assertArrayHasKey($key, $array2);
		
		if (isset($array2[$key]))
		{
			$keyPath = $rootPath;
			$keyPath[] = $key;
			
			if (is_array($value))
			{
				$this->assertArrayEquals($value, $array2[$key], $keyPath);
			}
			else
			{
				$this->assertEquals($value, $array2[$key], "Failed asserting that `".$array2[$key]."` matches expected `$value` for path `".implode(" > ", $keyPath)."`.");
			}
	    }
	}
}

Then to use it

$this->assertArrayEquals($array1, $array2, array("/"));

Solution 12 - Php

I wrote some simple code to first get all the keys from a multi-dimensional array:

 /**
 * Returns all keys from arrays with any number of levels
 * @param  array
 * @return array
 */
protected function getAllArrayKeys($array)
{
    $keys = array();
    foreach ($array as $key => $element) {
        $keys[] = $key;
        if (is_array($array[$key])) {
            $keys = array_merge($keys, $this->getAllArrayKeys($array[$key]));
        }
    }
    return $keys;
}

Then to test that they were structured the same regardless of the order of keys:

    $expectedKeys = $this->getAllArrayKeys($expectedData);
    $actualKeys = $this->getAllArrayKeys($actualData);
    $this->assertEmpty(array_diff($expectedKeys, $actualKeys));

HTH

Solution 13 - Php

If values are just int or strings, and no multiple level arrays....

Why not just sorting the arrays, convert them to string...

    $mapping = implode(',', array_sort($myArray));

    $list = implode(',', array_sort($myExpectedArray));

... and then compare string:

    $this->assertEquals($myExpectedArray, $myArray);

Solution 14 - Php

If you want test only the values of the array you can do:

$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));

Solution 15 - Php

Another option, as if you didn't already have enough, is to combine assertArraySubset combined with assertCount to make your assertion. So, your code would look something like.

self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);

This way you are order independent but still assert that all your elements are present.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkoenView Question on Stackoverflow
Solution 1 - PhppryazhnikovView Answer on Stackoverflow
Solution 2 - PhpCraigView Answer on Stackoverflow
Solution 3 - PhpValentin DespaView Answer on Stackoverflow
Solution 4 - Phprodrigo-silveiraView Answer on Stackoverflow
Solution 5 - PhpksimkaView Answer on Stackoverflow
Solution 6 - PhpRodney GitzelView Answer on Stackoverflow
Solution 7 - PhpCrisView Answer on Stackoverflow
Solution 8 - PhpAntonis CharalambousView Answer on Stackoverflow
Solution 9 - PhpcaligariView Answer on Stackoverflow
Solution 10 - PhptheintzView Answer on Stackoverflow
Solution 11 - Phpmoins52View Answer on Stackoverflow
Solution 12 - PhpsturrockadView Answer on Stackoverflow
Solution 13 - PhpkoalaokView Answer on Stackoverflow
Solution 14 - PhpAnderson ContreiraView Answer on Stackoverflow
Solution 15 - PhpJonathanView Answer on Stackoverflow