phpunit icon indicating copy to clipboard operation
phpunit copied to clipboard

assertSame ignoring key order

Open Seldaek opened this issue 11 months ago • 3 comments

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.

Seldaek avatar Dec 18 '24 16:12 Seldaek

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

mfn avatar Dec 19 '24 07:12 mfn

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.

Seldaek avatar Dec 19 '24 08:12 Seldaek

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.

mvorisek avatar Feb 11 '25 01:02 mvorisek