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

Custom ArgsMatcher #23

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MartinMystikJonas
Copy link

New version of pull request #9 rebased to current master.


Default object args matching based on spl_object_hash is not practical in case of value object, which equality is based only on properties values. Custom argsMatchers can replace default argsMatcher to support different matching policy.

Example of CustomArgsMatcher we currently use:

class CustomArgsMatcher extends \Mockista\ArgsMatcher {

  protected function hashArg($arg) {
    if($arg instanceof Entity) {
      // entites are matched based on values of their properties
      return md5(print_r($arg->getProperties()->toArray(), true));
    } elseif($arg instanceof \DateTime) {
      //two DateTime objects are equivalent it they represent same time
      return md5(print_r($arg->format("Y-m-d_H:i:se")), true);
    } elseif($arg instanceof \Nette\ArrayHash) {
      // ArrayHash wrapper of array is matched based on it's content
      return parent::hashArg((array)$arg);
    } else {
      // fallback for all other types
      return parent::hashArg($arg);
    }
  }

}

Usage:

$registry = new \Mockista\Registry(new CustomArgsMatcher()); 
// all mocks created by registry will use CustomArgsMatcher
$mock = $registry->create();
$mock->setArgsMatcher(new CustomArgsMatcher());
// change argsMatcher only for specific mock
$date1 = new \DateTime("2014-05-23 10:00:00");
$date2 = new \DateTime("2014-05-23 10:00:00");
$mock->expects('method')->with($date1)->once();
$mock->method($date2);
$mock->assertExpectations();
// assert is valid if CustomArgsMatcher compare DateTime only by value

@janmarek
Copy link
Owner

I really don't think that custom args matcher should be set globally.

What about something like:

interface ArgsMatcher
{
    /** @return bool */
    match($arg);
}

class DateMatcher implements ArgsMatcher
{
    // ...
}

$mock->expect('method')->with(new DateMatcher("2014-05-23 10:00:00"))->once();

@MartinMystikJonas
Copy link
Author

I think it is very practical to set it globally if you know that some types are value types you want always checked only by their value.

For value types you will need to remember to use custom matcher in every expect. If you forgot then you get brittle tests which will fail if you change implementation details (for example if you for some reason re-create value object instead of passsing it through).

Another common issue is if parameter is created inside object you testing and then passed as parameter in call to mock. In this case you cannot use identity matching, because instance is created somewhere else. But you want simple way how to test that passed argument has correct value.

Take for example DateTime. There is no situation in which you care what instance was used. All you care about is what time it represents.

This has to be used carefully but it will simplify lot of tests if you use value objects.

@MartinMystikJonas
Copy link
Author

Basically this if workaround for missing equals() magic methods which we have for example in Java.

@f3l1x
Copy link
Collaborator

f3l1x commented Mar 15, 2017

@MartinMystikJonas Hi. Is this ticket relevant ant this moment?

@MartinMystikJonas
Copy link
Author

We still use this in our fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants