phpunit
phpunit copied to clipboard
assertSame ignoring key order
Key order when we're talking about associative arrays is quite often irrelevant, especially when checking stuff that will end up being json serialized where dictionaries do not guarantee the key order.
Right now I'm stuck between using assertEqualsCanonicalizing and risking datatype errors, e.g.:
$this->assertEqualsCanonicalizing(['foo' => 1], ['foo' => '1']); // true
Or use assertSame but then key order tends to make tests extra brittle. Having an option for assertSame or a new assertDictionarySame or something would be great.
I've been working with JSON api stuff for years and always required this (I wanted the assertSame safety so badly) and almost a decade ago came up with this, which I still use today and it hardly changed. May it be useful to others or serve as inspiration:
/**
* Asserts that the non-array values of the arrays are the same
* (using assertSame) but the order of keys within the arrays
* does not matter.
*
* Implementation detail:
* - uses SORT_NATURAL for sorting keys so that the order for keys like
* 0 and "a" are defined (without it, sort will return them in order
* of 0,"a" or "a",0).
*
* Inspired by http://stackoverflow.com/a/33029270/47573
*
* @param array $aryPath Only used internally for constructing the
* assert message helping to figure out where the arrays differ.
*/
public static function assertArraySame(array $expectedAry, array $actualAry, array $aryPath = ['/']): void
{
if (['/'] === $aryPath) {
// The built-in assert has very useful diffing capability.
// By first checking whether the array "equals" we
// already established lots of tests we would
// otherwise # need to do ourselves (e.g.
// checking if no keys are missing, etc.)
Assert::assertEquals($expectedAry, $actualAry);
}
foreach ($expectedAry as $expectedKey => $expectedValue) {
$actualValue = $actualAry[$expectedKey];
$currentPath = $aryPath;
$currentPath[] = $expectedKey;
if (\is_object($expectedValue) && \is_object($actualValue)) {
Assert::assertEquals($expectedValue, $actualValue);
continue;
}
if (\is_array($expectedValue) && \is_array($actualValue)) {
static::assertArraySame($expectedValue, $actualValue, $currentPath);
continue;
}
Assert::assertSame(
$expectedValue,
$actualValue,
'Failed asserting that values in array at ' . implode('/', $currentPath) . ' are the same.'
);
}
}
Thanks for sharing the code. This indeed is probably not that hard to implement, but I believe it might qualify as something worth having built-in, which is why I opened the issue.
As assertSame asserts type and if object instances are the same, I would say this is a bug as I would expect the key order to be asserted as well.