Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assert that arrays (or objects) are equal while excluding certain keys (or properties) #5108

Open
NolwennD opened this issue Nov 30, 2022 · 6 comments
Labels
feature/assertion Issues related to assertions and expectations type/enhancement A new idea that should be implemented

Comments

@NolwennD
Copy link

Some object may have properties with unpredictable values, like random UUID, and you want to compare the predictable ones.

In Java, with AssertJ it’s possible with: assertThat(actual).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected).

Maybe a signature like assertEqualsIgnoringFields($expected, $actual, string ...$fields)

<?php

class Foo {

    private readonly DateTimeImmutable $date;
    public function __construct(private readonly string $name)
    {
        $this->date = new DateTimeImmutable();
    }
}

/**
* @test
*/
public function passingTest(): void
{
    $foo1 = new Foo("fooName");
    $foo2 = new Foo("fooName");

    self::assertEqualsIgnoringFields($foo1, $foo2, "date");
}
@NolwennD NolwennD added the type/enhancement A new idea that should be implemented label Nov 30, 2022
@sebastianbergmann sebastianbergmann added the feature/assertion Issues related to assertions and expectations label Nov 30, 2022
@sebastianbergmann
Copy link
Owner

My suggestion would be to implement Foo::equals() and then use assertObjectEquals().

@sebastianbergmann sebastianbergmann changed the title Create a new assertion, assertEqualsIgnoringFields Assert that arrays (or objects) are equal while excluding certain keys (or properties) Jan 25, 2023
@sebastianbergmann sebastianbergmann added this to the PHPUnit 10.1 milestone Jan 25, 2023
@lolli42
Copy link
Contributor

lolli42 commented Feb 5, 2023

This can be done with a custom comparator, if I understand the issue correctly.
They are surprisingly simple to implement, see
https://stackoverflow.com/questions/43589561/phpunit-getting-equalto-assertion-to-ignore-property
for an example that worked for me.

@sebastianbergmann
Copy link
Owner

sebastianbergmann commented Feb 5, 2023

@lolli42 You are correct. I am still undecided whether or not I want to implement this or not.

@sebastianbergmann sebastianbergmann removed this from the PHPUnit 10.1 milestone Mar 30, 2023
@gnutix
Copy link

gnutix commented Sep 19, 2023

I have quite a complex immutable value object representing a time interval (like 2023-09-19T16:43:12/2023-09-19T23:59). Some of the methods (isEmpty, getInclusiveEnd, etc) have quite an impact on performances if they are called a big number of times.

So I thought: let's add a private property that acts as a cache for these values (as they can never change for a given instance of the object), and they'll be computed only once the first time we need them. Like :

private bool $isEmpty;

public function isEmpty(): bool
{
    return $this->isEmpty ??=
        null !== $this->start && null !== $this->end && $this->start->equals($this->end);
}

What I did not expect was that now most of my unit tests are broken, because we compare objects (and array of objects, and objects containing arrays of objects,... you get the gist) quite a lot, and now sometimes these cache properties will be set, and sometimes they wont (depending on whether or not the method was called at least once before the comparison). Which is then seen as a difference by assertEquals/assertSame :

Failed asserting that two objects are equal.
--- Expected
+++ Actual
@@ @@
             0 => Gammadia\DateTimeExtra\LocalDateTimeInterval Object (
                 'start' => Brick\DateTime\LocalDateTime Object (...)
                 'end' => Brick\DateTime\LocalDateTime Object (...)
-                'isEmpty' => false
             )
             1 => 1
         )
     )
 )

@sebastianbergmann Is there a way to tell PHPUnit to globally ignore these cache-related private properties when doing comparisons (so not by registering some custom comparator in each test or changing the assert method) ? Something like the #[Exclude] PHP attribute in Symfony Serializer or such ?

Or is there another way to have this cache without the comparison issue that I'm not seeing ?

@sebastianbergmann
Copy link
Owner

No, you "only" have the two options PHPUnit provides for this so far: registering a custom comparator or using assertObjectEquals() which in turn uses your value object's equals() or equalTo() method. IMO, the latter is preferable over the former.

@gnutix
Copy link

gnutix commented Sep 19, 2023

Thanks for your answer. I went the assertObjectEquals road. I had to write a quite weird implementation of equals on another object that uses LocalDateTimeInterval internally, but otherwise it went okay and works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/assertion Issues related to assertions and expectations type/enhancement A new idea that should be implemented
Projects
None yet
Development

No branches or pull requests

4 participants