2

I'm testing with PHPSpec, and I've got custom matchers in my spec files that I don't want to have to repeat in every file in which they're used.

I want to be able to extend PHPSpec's behaviour and/or the behaviour of all my tests to use a global set of custom matchers.

The PHPSpec documentation shows how to create custom matchers (inline matchers): http://phpspec.readthedocs.org/en/latest/cookbook/matchers.html#inline-matcher

I have this inline matcher (as an example) in my ThingImTestingSpec class:

public function getMatchers()
    {
        return [
            'haveSecondLevelKey' => function ($subject, $key) {
                foreach ($subject as $first_level_key => $second_level_array)
                {
                    if (is_array($second_level_array))
                    {
                        return array_key_exists($key, $second_level_array);
                    }
                }
                return FALSE;
            }
        ];
    }

This inline matcher example detects whether an array has an array as one of its values, and returns true or false.

As far as I can tell, PHPSpec calls getMatchers() when it constructs ThingImTestingSpec, if a getMatchers() method is present (otherwise PHPSpec ends up calling the empty getMatchers() method on ObjectBehavior.

What I've tried:

Create a CustomMatchers class and namespace it:

namespace SpecUtilities;

class CustomMatchers
{
    public static function getMatchers()
    { ... }
}

and add this to my spec file:

use SpecUtilities\CustomMatchers

and in the class itself:

function it_pulls_in_custom_matchers()
{
    CustomMatchers::getMatchers();
}

but the return from getMatchers() when the tests are run doesn't get used for anything. PHPSpec only seems to be using the return from getMatchers() when it constructs the test (which makes sense - getMatchers() only returns an array of functions; it doesn't attach those functions to anything else, so PHPSpec isn't using them). I get the error

no haveSecondLevelKey([array:1]) matcher found for [array:14].

i.e. PHPSpec isn't loading the custom matchers.

All spec classes extend ObjectBehavior. I could add my getMatchers() function to PHPSpec's ObjectBehavior class, but I don't want to modify files under /vendor (PHPSpec is being pulled in using Composer). I could copy the vendor file and modify my own CustomObjectBehavior class, and make my spec classes extend that instead, but that will break the usability of PHPSpec's generator methods like phpspec describe SomeNewSpec (I'll have to change the class that new specs extend every time I generate a new spec).

Am I asking too much? Short of modifying ObjectBehavior itself to look for and load an external custom matchers file, and making a pull request for the PHPSpec repo itself, is there a way to load custom matchers without getting into bad practices?

Rick Gladwin
  • 4,225
  • 1
  • 17
  • 21

2 Answers2

1

I've used an extension "phpspec-matcher-loader-extension" for this purpose -- it's worked well for me. From the description:

Allows custom phpspec matchers to be registered globally via configuration

0

There are two ways to get the result you want:

a) Extend ObjectBehaviour and add a new default getMatchers, then extend your new class instead of ObjectBehaviour. You can use a custom template to ensure that describe then generates classes extending the right object (see how PhpSpec itself uses a custom template https://github.com/phpspec/phpspec/tree/master/.phpspec)

b) Write your Matchers as objects implementing PhpSpec\Matcher\MatcherInterface then write an Extension that registers your custom matchers with PhpSpec. This is more complex and requires some understanding of how Matchers are registered.

We're thinking about how to make this easier in future releases, maybe via some configuration settings.

Ciaran McNulty
  • 18,698
  • 6
  • 32
  • 40
  • 1
    Could you follow up on this? The documentation says you can autoload with a configuration setting, but there is no information on *how* to properly extend PhpSpec\Matcher\Matcher. – musicin3d Aug 21 '17 at 04:05