I have the following PHPUnit testcase:
$mailer = $this->getMockBuilder(MailerInterface::class)->getMock();
$simpleMailer = new SimpleMailer($mailer);
$message = (new Message())
->setTo($user)
->setFrom($from)
->setSubject($subject)
->setTemplate(SimpleMailer::TEMPLATE)
->setContext(['message' => $body]);
if ($bcc) { $message->addBcc($bcc); }
$mailer
->expects($this->once())
->method('send')
->with($this->equalTo($message));
$simpleMailer->sendMessage($user, $subject, $body, $from, $bcc);
This was working fine until the Message class was changed. The Message class now sets a unique ID on construction, meaning that equalTo now returns false with the following difference:
MailerBundle\Document\Message Object (
- 'id' => '5a372f3c-a8a9-4e1e-913f-d756244c8e52'
+ 'id' => '11176427-7d74-4a3c-8708-0026ae666f8b'
'type' => null
'user' => Tests\TestUser Object (...)
'toName' => ''
'toAddress' => null
'domain' => null
'fromName' => null
'fromAddress' => 'user@example.org'
'bccAddresses' => Array (...)
'subject' => 'subject'
'textBody' => null
'htmlBody' => null
'template' => 'MailerBundle:MailTemplates:...l.twig'
'context' => Array (...)
)
Is there any way that I can exclude certain properties from the equality check?
If your Message class has getters, you can use a callback in with function to match only properties you care about. Something similar to
$mailer
->expects($this->once())
->method('send')
->with($this->callback(function(Message $message) use ($user, $from, $subject, $body) {
return $message->getTo() == $user
&& $message->getFrom() == $from
&& $message->getSubject() == $subject
&& $message->getTemplate() == SimpleMailer::TEMPLATE
&& $message->getContext()['message'] == $body
}));
I just wrote something similar, and was curious if I just reinvented the wheel (I pretty much did). However, I thought would share my result here in case it helps anyone, since it matches your use case like mine.
My goal was to compare two objects and assert equality between properties, with the ability to ignore specified keys. I'm using this with the current PHPunit (Feb 2020).
private function assertEqualWithIgnore($expected, $actual, $ignoreKeys = [], $currentKey = null): void
{
if (is_object($expected)) {
foreach ($expected as $key => $value) {
$this->assertEqualWithIgnore($expected->$key, $actual->$key, $ignoreKeys, $key);
}
} elseif (is_array($expected)) {
foreach ($expected as $key => $value) {
$this->assertEqualWithIgnore($expected[$key], $actual[$key], $ignoreKeys, $key);
}
} elseif ($currentKey !== null && !in_array($currentKey, $ignoreKeys)) {
$this->assertEquals($expected, $actual);
}
}
Inspired by Brian's answer, here is an helper that trim the unwanted ids before making an equality check. This way the behavior remains very close to phpunit's assertEquals.
You get the equality checks on all properties at once instead of one at the time. You can also more easily spot where the check is failing.
private function assertEqualsButIgnore($expected, $actual, $ignoreKeys = []): void
{
$copyExpected = $expected;
$this->recursiveUnset($copyExpected, $ignoreKeys);
$copyActual = $actual;
$this->recursiveUnset($copyActual, $ignoreKeys);
$this->assertEquals($copyExpected, $copyActual);
}
private function recursiveUnset(&$objOrArray, $unwanted_key): void
{
foreach($unwanted_key as $key) {
if(is_array($objOrArray)) {
unset($objOrArray[$key]);
} else {
unset($objOrArray->$key);
}
}
foreach ($objOrArray as &$value) {
if (is_array($value) || is_object(($value))) {
$this->recursiveUnset($value, $unwanted_key);
}
}
}