Phony

Phony

Installation

Available as various Composer packages, depending on the test framework in use:

See Integration with test frameworks.

Guides

In addition to this documentation, the following guides are available, focusing on particular topics related to Phony:

Help

For help with a difficult testing scenario, questions regarding how to use Phony, or to report issues with Phony itself, please open a GitHub issue so that others may benefit from the outcome.

Alternatively, @ezzatron may be contacted directly via Twitter.

Usage

Using this documentation

Example test suites

See the phony-examples repository.

Standalone usage

Install the eloquent/phony package, then:

use function Eloquent\Phony\mock;

$handle = mock(ClassA::class);
$handle->methodA->with('argument')->returns('value');

$mock = $handle->get();

assert($mock->methodA('argument') === 'value');
$handle->methodA->calledWith('argument');

Kahlan usage

Install the eloquent/phony-kahlan package, then:

use function Eloquent\Phony\Kahlan\mock;

describe('Phony', function () {
    it('integrates with Kahlan', function () {
        $handle = mock('ClassA');
        $handle->methodA->with('argument')->returns('value');

        $mock = $handle->get();

        expect($mock->methodA('argument'))->toBe('value');
        $handle->methodA->calledWith('argument');
    });
});

The eloquent/phony-kahlan package also provides auto-wired mocks:

use function Eloquent\Phony\Kahlan\on;

describe('Phony for Kahlan', function () {
    it('supports auto-wiring', function (ClassA $mock) {
        $handle = on($mock);
        $handle->methodA->with('argument')->returns('value');

        expect($mock->methodA('argument'))->toBe('value');
        $handle->methodA->calledWith('argument');
    });
});

Peridot usage

Install the eloquent/phony-peridot package, then:

use function Eloquent\Phony\mock;

describe('Phony', function () {
    it('integrates with Peridot', function () {
        $handle = mock(ClassA::class);
        $handle->methodA->with('argument')->returns('value');

        $mock = $handle->get();

        expect($mock->methodA('argument'))->to->equal('value');
        $handle->methodA->calledWith('argument');
    });
});

The eloquent/phony-peridot package also provides auto-wired mocks:

use function Eloquent\Phony\on;

describe('Phony for Peridot', function () {
    it('supports auto-wiring', function (ClassA $mock) {
        $handle = on($mock);
        $handle->methodA->with('argument')->returns('value');

        expect($mock->methodA('argument'))->to->equal('value');
        $handle->methodA->calledWith('argument');
    });
});

PHPUnit usage

Install the eloquent/phony-phpunit package, then:

use Eloquent\Phony\Phpunit\Phony;

class PhonyTest extends PHPUnit_Framework_TestCase
{
    public function testIntegration()
    {
        $handle = Phony::mock(ClassA::class);
        $handle->methodA->with('argument')->returns('value');

        $mock = $handle->get();

        $this->assertSame('value', $mock->methodA('argument'));
        $handle->methodA->calledWith('argument');
    }
}

Integration with test frameworks

In order to provide the easiest integration with test frameworks, Phony exposes the same API through multiple namespaces. Integration is as simple as picking the correct Composer package for the framework in use, and importing the relevant namespace:

Importing

There are two ways to import Phony's API. The most appropriate choice will depend on the test framework in use, and the user's preferred coding style.

Importing with use function

If the version of PHP in use supports use function, the top-level functions can be imported from the appropriate namespace and used directly:

use function Eloquent\Phony\mock;

$handle = mock(ClassA::class);

Importing without use function

A static facade implementation is also provided for those who prefer a more "traditional" approach:

use Eloquent\Phony\Phony;

$handle = Phony::mock(ClassA::class);

Mocks

Mocks are objects that can be used as a substitute for another object. This can be useful when a "real" object becomes difficult to use in a test.

Mocking basics

Any class, interface, or trait can be mocked. To create a mock, use mock():

$handle = mock(ClassA::class);        // with `use function`
$handle = Phony::mock(ClassA::class); // without `use function`

The object returned by mock() is not the mock object itself, but a mock handle. This handle provides a stub for each method of the type being mocked. Each stub is exposed as a property of the same name as the stubbed method, and implements both the stub API, and the spy API:

// stubbing
$handle->methodA->returns('1337 h4x');
$handle->methodB->with('bad', 'input')->throws('You done goofed.');

// verification
$handle->methodA->calledWith('swiggity', 'swooty');
$handle->methodB->called();

To access the actual mock object, call the get() method of the handle:

$mock = $handle->get();

Partial mocks

Phony supports "partial mocks", or mocks that do not override methods by default. To create a partial mock, use partialMock():

$handle = partialMock(ClassA::class);        // with `use function`
$handle = Phony::partialMock(ClassA::class); // without `use function`

Constructor arguments can be passed to partialMock() as the second parameter:

$handle = partialMock(ClassA::class, ['argumentA', 'argumentB']);

Proxy mocks

In cases where direct mocking is not possible, such as with final classes and methods, Phony offers an alternative strategy in the form of "proxy" mocks. Any mock can proxy methods on to any other object by using proxy():

interface Animal
{
    public function speak();
}

final class Cat implements Animal
{
    final public function speak()
    {
        return 'Meow meow meow? Meow.';
    }
}

function listen(Animal $animal)
{
    echo 'It said: ' . $animal->speak();
}

$cat = new Cat();

$handle = mock(Animal::class); // a generic animal mock
$handle->proxy($cat);          // now it behaves exactly like `$cat`

listen($handle->get());        // outputs 'It said: Meow meow meow? Meow.'

The proxy() method is also fluent, meaning that mock creation and proxying can be done in a single expression:

$handle = mock(Animal::class)->proxy(new Cat());

Mocking multiple types

Multiple interfaces and/or traits can be mocked simultaneously by passing an array of types to mock() or partialMock():

$handle = mock([InterfaceA::class, InterfaceB::class, TraitA::class]);        // with `use function`
$handle = Phony::mock([InterfaceA::class, InterfaceB::class, TraitA::class]); // without `use function`

A single base class may also be mocked with other types:

$handle = mock([ClassA::class, InterfaceA::class, TraitA::class]);        // with `use function`
$handle = Phony::mock([ClassA::class, InterfaceA::class, TraitA::class]); // without `use function`

Ad hoc mocks

Phony supports the creation of mock objects with methods and/or properties that are not pre-defined in some other class, interface, or trait. It does so using special "definition" values, which can be passed to mock() or partialMock():

$handle = partialMock(
    [
        '__toString' => function () {
            return 'What is this sorcery?';
        },
        '__call' => function ($name, array $arguments) {
            return sprintf('%s(%s)', $name, implode(', ', $arguments));
        },
    ]
);

$mock = $handle->get();

echo $mock;              // outputs 'What is this sorcery?'
echo $mock->a('b', 'c'); // outputs 'a(b, c)'

Definition values can be mocked at the same time as regular classes, traits, and interfaces. Multiple definitions can even be mocked at the same time. Simply specify all types to be mocked in a single array:

$handle = partialMock(
    [
        'Countable',
        [
            '__invoke' => function () {
                return 'You called me?';
            }
        ],
        [
            '__toString' => function () {
                return 'Are you stringing me along?';
            }
        ],
    ]
);

$handle->count->returns(111);
$mock = $handle->get();

echo $mock();      // outputs 'You called me?'
echo $mock;        // outputs 'Are you stringing me along?'
echo count($mock); // outputs '111'

Ad hoc definition values

Ad hoc definition values support methods, properties, and constants. By default, callback functions will be converted to methods, and everything else will become a property:

$handle = partialMock(
    [
        'a' => function () {
            return 'A is for apple.';
        },
        'b' => 'B is for banana.',
    ]
);

$mock = $handle->get();

echo $mock->a(); // outputs 'A is for apple.'
echo $mock->b;   // outputs 'B is for banana.'

Extra information can be added to the keys of the definition value for cases where it's desirable to:

$handle = partialMock(
    [
        'const A' => 'A is for apple.',
        'static b' => function () {
            return 'B is for banana.';
        },
        'static c' => 'C is for cat.',
        'string d' => 'D is for dog.'
        'function e' => 'implode',
    ]
);

$mock = $handle->get();
$class = get_class($mock);

echo $class::A;   // outputs 'A is for apple.'
echo $class::b(); // outputs 'B is for banana.'
echo $class::c;   // outputs 'C is for cat.'
echo $mock->d;    // outputs 'D is for dog.'

echo $mock->e(', ', ['a', 'b']); // outputs 'a, b'

Ad hoc definition magic "self" values

Methods defined in an ad hoc mock definition can take advantage of magic "self" values. When stubs are retrieved from a mock, their self value is automatically set to the mock itself.

Any custom method with a first parameter named $phonySelf, regardless of the parameter's type, will receive the self value as the first argument. This self value can be used in place of $this to access instance state, and/or implement fluent interfaces:

$handle = partialMock(
    [
        'set' => function ($phonySelf, $key, $value) {
            $phonySelf->values[$key] = $value;

            return $phonySelf;
        },
        'get' => function ($phonySelf, $key) {
            return $phonySelf->values[$key] ?? null;
        },
        'values' => [],
    ]
);

$mock = $handle->get();

echo $mock->set('a', 1)->get('a'); // outputs '1'

Static mocks

Phony can be used to stub the behavior of static methods of generated mock classes. To modify the behavior of a static method, use onStatic() to obtain a static handle from either an existing handle, or a mock instance:

$handle = mock(DateTime::class);
$mock = $handle->get();

$static = onStatic($handle);        // with `use function`
$static = Phony::onStatic($handle); // without `use function`

$static = onStatic($mock);        // with `use function`
$static = Phony::onStatic($mock); // without `use function`

This static handle is just like a normal mock handle, except that it refers to static methods instead of instance methods:

$static->createFromFormat->returns(new DateTime('2001-02-03T04:05:06Z'));

$class = $static->className(); // obtain the generated class name

$date = $class::createFromFormat('format', 'time');
echo $date->format('c'); // outputs '2001-02-03T04:05:06+00:00'

The static handle can also be used to verify interactions with static methods:

$static->createFromFormat->calledWith('format', 'time');

Calling a constructor manually

In order to stub methods called in the constructor of a partial mock, it is necessary to defer construction of the mock object. To accomplish this, pass null as the second argument to partialMock(), which will cause Phony to bypass the constructor:

$handle = partialMock(ClassA::class, null);

Note that omitting the second argument will not have the same effect as explicitly passing null.

Behavior can then be defined before the constructor is called:

$handle->methodA->returns(true);

Finally, the constructor can be manually called using construct(), or constructWith():

$handle->construct('argumentA', 'argumentB');       // variable arguments
$handle->constructWith(['argumentA', 'argumentB']); // array arguments

The constructWith() additionally supports arguments passed by reference:

$a = null;
$b = null;

$handle->constructWith([&$a, &$b]);

Labeling mocks

Every mock has a label, which is a free-form string used to help identify the mock in verification failure messages. By default, each mock is assigned a unique sequential integer label upon creation.

The label can be changed at any time by using setLabel() on any mock handle associated with the mock:

$handle = mock(ClassA::class);
$mock = $handle->get();

$handle->setLabel('a');

echo $handle->label();   // outputs 'a'
echo on($mock)->label(); // outputs 'a'

The setLabel() method is also fluent, meaning that mock creation and label setting can be done in a single expression:

$mock = mock(ClassA::class)->setLabel('a')->get();

When a verification fails for a labeled mock, the output is similar to the following:

Example verification output when using a mock label

Mock handles

Mock handles are objects that provide access to stubs for each method of a mock object. Each method stub implements both the stub API for stubbing, and the spy API for verification.

Mock handles are returned when mock() or partialMock() is called:

$handle = mock(ClassA::class);
$handle = partialMock(ClassA::class);

They can also be retrieved at any time from a mock instance, or another handle, by using on():

$handle = on($mock);        // with `use function`
$handle = Phony::on($mock); // without `use function`

$handle = on($otherHandle);        // with `use function`
$handle = Phony::on($otherHandle); // without `use function`

To access the actual mock object, call the get() method of the handle:

$mock = $handle->get();

Note that a static handle variant exists. See Static mocks.

Mock handle substitution

Phony will sometimes accept a mock handle as equivalent to the mock it represents. This simplifies some common mocking scenarios, and improves test readability.

One such scenario is returning a mock from another stub (this includes stubbed mock methods). Returning a handle from a stub is equivalent to returning the mock itself:

$database = mock(Database::class);
$result = mock(Result::class);

// these two statements are equivalent
$database->select->returns($result);
$database->select->returns($result->get());

Another common situation is the use of a mock handle when matching stub arguments. Use of a mock handle in a argument list is equivalent to use of the mock itself:

$database = mock(Database::class);
$query = mock(Query::class);
$result = mock(Result::class);

// these two statements are equivalent
$database->select->with($query)->returns($result);
$database->select->with($query->get())->returns($result);

The same is true when verifying that a spy was called with specific arguments:

$database = mock(Database::class);
$query = mock(Query::class);

// these two statements are equivalent
$database->select->calledWith($query);
$database->select->calledWith($query->get());

There are other edge-case situations where Phony will exhibit this behavior. Refer to the API documentation for more detailed information.

Mock builders

Mock builders provide an alternative method for defining and creating mocks, when more fine-grained control is desired. To create a mock builder, use mockBuilder():

$builder = mockBuilder();        // with `use function`
$builder = Phony::mockBuilder(); // without `use function`

Types to mock can be passed directly to mockBuilder() in a similar fashion to mock():

$builder = mockBuilder([ClassA::class, InterfaceA::class]);

Customizing the mock class

Mock builders implement a fluent interface, with many methods for customizing the generated mock class:

$builder
    ->like(ClassA::class, InterfaceA::class)
    ->named('CustomClassName')
    ->addMethod(
        'methodA',
        function ($argumentA, &$argumentB) {
            // ...
        }
    )
    ->addProperty('propertyA', 'a', 'string')

This is only a small example of what is possible. For a full list of the available methods, see the mock builder API.

Creating mocks from a builder

Once the builder is configured, there are several options for creating mock instances. All of these will internally "finalize" the mock builder, and no further customizations can be made.

Use full() to create a new full mock instance:

$mock = $builder->full();

Use partial() to create a new partial mock instance:

$mock = $builder->partial();

Constructor arguments can also be passed to partial():

$mock = $builder->partial('a', 'b');

There is also a more advanced method, partialWith(), that accepts arguments passed by reference:

$a = null;
$b = null;

$mock = $builder->partialWith([&$a, &$b]);

All of the above methods for creating mock instances store the last created mock instance on the builder. To retrieve the last created mock instance, use get():

$mockA = $builder->full();
$mockB = $builder->get();

echo $mockA === $mockB ? 'true' : 'false'; // outputs 'true'

If no mock instance already exists, get() will create a new full mock instance, and return it.

Note that unlike using mock(), these methods do not automatically wrap the returned mock in a mock handle. To obtain a handle, use on():

$mock = $builder->mock();
$handle = on($mock);

Generating mock classes from a builder

The mock class can be generated without actually creating a mock instance. To generate and return a mock class, use build(), (which returns a ReflectionClass):

$class = $builder->build();

The build() method will normally return the same class for subsequent calls. To generate a new class each time, pass true as the first argument:

$classA = $builder->build(true);
$classB = $builder->build(true);

Note that this is not possible when a custom class name has been set.

If only the class name is required, className() will generate the mock class, and return the class name as a string:

$className = $builder->className();

All of the above methods will internally "finalize" the mock builder, and no further customizations can be made.

Copying mock builders

Mock builders can be copied by using the clone operator:

$builderA = mockBuilder();
$builderB = clone $builderA;

A copied mock builder can be modified even if the original mock builder is "finalized":

$countableBuilder = mockBuilder('Countable');
$countable = $countableBuilder->get();

$countableIteratorBuilder = clone $countableBuilder;
$countableIteratorBuilder->like('Iterator');
$countableIterator = $countableIteratorBuilder->get();

echo $countable instanceof Countable ? 'true' : 'false';         // outputs 'true'
echo $countableIterator instanceof Countable ? 'true' : 'false'; // outputs 'true'

echo $countable instanceof Iterator ? 'true' : 'false';         // outputs 'false'
echo $countableIterator instanceof Iterator ? 'true' : 'false'; // outputs 'true'

The copied mock builder will also ignore any mock instances generated by the original builder:

$builderA = mockBuilder();
$mockA = $builderA->get();

$builderB = clone $builderA;
$mockB = $builderB->get();

echo $mockA === $mockB ? 'true' : 'false'; // outputs 'false'

Pausing mock recording

To temporarily prevent an entire mock from recording calls, use stopRecording() and startRecording() as necessary:

$handle = mock(
    [
        'methodA' => function () {},
    ]
);
$mock = $handle->get();

$mock->methodA('a');
$handle->stopRecording();
$mock->methodA('b');
$handle->startRecording();
$mock->methodA('c');

$handle->methodA->calledWith('a'); // passes
$handle->methodA->calledWith('c'); // passes

$handle->methodA->calledWith('b'); // fails

Mocking and non-public methods

Phony's mocks do not require special setup for protected methods. Stubbing and verification work exactly the same for protected methods as they do for public methods:

class Cat
{
    public function speak()
    {
        echo $this->think();
    }

    protected function think()
    {
        return 'Meow.';
    }
}

$handle = partialMock(Cat::class);
$cat = $handle->get();

$cat->speak();            // outputs 'Meow.'
$handle->think->called(); // passes

$handle->think->returns('Cower in fear, mortal.');

$cat->speak(); // outputs 'Cower in fear, mortal.'

Note however, that Phony does not magically make non-public methods accessible from outside the class. See Accessing non-public methods and properties for how to achieve this.

Also note that Phony cannot mock private methods. This is because the mock class generated by Phony is a sub-class of the original type, and "overriding" a private method does not change the behavior of the original method.

Accessing non-public methods and properties

In some cases, it may be necessary to bypass the visibility restrictions imposed by the protected and private keywords in order to test a particularly difficult-to-reach branch of code.

This should be considered a code smell, and ideally the system under test should be refactored to avoid the need for such measures. If all else fails, protected and private keywords can effectively be bypassed.

Phony does not help in these kinds of cases, because it does not change the visibility of methods when mocking. That is, public methods remain public, and protected methods remain protected.

The Liberator library is specifically designed to aid in circumventing protected and private keywords to provide access to normally inaccessible methods and properties.

It is also possible to bypass visibility restrictions, without a library, via reflection:

class Cat
{
    protected function think()
    {
        return $this->thought;
    }

    private $thought = 'Meow.';
}

$cat = new Cat();

$think = new ReflectionMethod($cat, 'think');
$think->setAccessible(true);
echo $think->invoke($cat); // outputs 'Meow.'

$thought = new ReflectionProperty($cat, 'thought');
$thought->setAccessible(true);
echo $thought->getValue($cat); // outputs 'Meow.'

Mocking problematic classes

Some PHP classes can be difficult, if not impossible, to mock. In particular, final classes can only be mocked via proxy mocks, and only in certain circumstances.

For up-to-date information on known issues with other problematic classes, see Mocking problematic classes in the wiki.

Terminology

By some popular definitions, Phony's mocks are not technically mocks at all, because they do not implement "expectations". According to these definitions, Phony's mocks would be more correctly called stubs.

However, throughout Phony's API and documentation, the term "mock" is used to refer to any test double that is an object. The term "stub" is used to refer to a callable that can be programmed to provide canned answers to incoming calls.

Stubs

Stubs are callable entities that can be configured to behave according to a set of rules when called. In Phony, every stub also implements the spy API.

Stubbing an existing callable

Any callable can be stubbed, by passing the callable to stub():

$stub = stub($callable);        // with `use function`
$stub = Phony::stub($callable); // without `use function`

By default, the created stub will return "empty" values. The exact value depends on the return type of the callable, but in many cases it will be null:

$stubA = stub(
    function () {
        return 'a';
    }
);
$stubB = stub(
    function (): int {
        return 111;
    }
);

var_dump($stubA()); // outputs 'NULL'
var_dump($stubB()); // outputs 'int(0)'

See Default values for return types for more information, and emptyValue() for a full list of types and associated "empty" values.

The stub can be configured to behave differently in specific circumstances, whilst falling back to the default behavior during regular operation:

$stub = stub('max')->with(2, 3, 1)->returns(9);

var_dump($stub(1, 2, 3)); // outputs 'NULL'
var_dump($stub(2, 3, 1)); // outputs 'int(9)'

The stub can also be configured to behave exactly like the original callable:

$stub = stub('max')->forwards();

var_dump($stub(1, 2, 3)); // outputs 'int(3)'
var_dump($stub(4, 5, 6)); // outputs 'int(6)'

Stubbing global functions

When an "unqualified" function (one with no preceding backslash) is used from within a namespace, PHP will attempt to find the function in the calling namespace first, before looking for the function in the global namespace. This behavior is known as global function fallback.

This behavior can be exploited to allow a stub to replace a global function during testing. To do so, two conditions must be met:

To stub a function in the global namespace, use stubGlobal():

$stub = stubGlobal($function, $namespace);        // with `use function`
$stub = Phony::stubGlobal($function, $namespace); // without `use function`

Where $function is the name of the function in the global namespace, and $namespace is the namespace from which the function will be called.

To demonstrate, the following code will work well with stubGlobal():

namespace Foo\Bar;

printf('Keep it %s.', 'real');

Under normal conditions, this code would output:

Keep it real.

But if the printf() function is stubbed before executing this code, its behavior can be changed:

namespace Foo\Bar;
use function Eloquent\Phony\stubGlobal;

$stub = stubGlobal('printf', __NAMESPACE__)->returns("You're a total phony!");

printf('Keep it %s.', 'real');

This code will now output:

You're a total phony!
Restoring global functions after stubbing

Leaving global functions stubbed after a test can affect the results of other tests in the same namespace that make use of the same global function. To avoid this problem, it is necessary to restore the behavior of stubbed global functions after the test concludes.

To restore all stubbed and/or spied global functions, use restoreGlobalFunctions():

restoreGlobalFunctions();        // with `use function`
Phony::restoreGlobalFunctions(); // without `use function`

This method is suitable for use in the "tear down" phase of a test.

Alternatives for stubbing global functions

If the system under test is not suitable for stubbing via global function fallback, an alternative is to accept a callback via dependency injection that can take a stub during testing:

use function Eloquent\Phony\stub;

function functionA($sprintf = 'sprintf')
{
    echo $sprintf('Keep it %s.', 'real');
}

$stub = stub('sprintf')->returns("You're a total phony!");

functionA();      // outputs 'Keep it real.'
functionA($stub); // outputs "You're a total phony!"

Another alternative is to use a library like Isolator, that allows the injection of an explicit dependency that represents the functions in the global namespace. This dependency can then be mocked during testing.

Anonymous stubs

Anonymous stubs are stubs that do not wrap an existing callable. An anonymous stub is created by calling stub() without passing a callable:

$stub = stub();        // with `use function`
$stub = Phony::stub(); // without `use function`

By default, anonymous stubs will always return null regardless of input arguments:

$stub = stub();

echo gettype($stub('a')); // outputs 'NULL'

The stub can be configured to behave differently in specific circumstances, whilst returning null during regular operation:

$stub = stub()->with('b')->returns('x');

echo gettype($stub('a')); // outputs 'NULL'
echo $stub('b');          // outputs 'x'

The stub can also be configured to behave the same in all circumstances:

$stub = stub()->returns('x');

echo $stub();    // outputs 'x'
echo $stub('a'); // outputs 'x'

Stub "self" values

All stubs have a special "self" value that is used in multiple ways by Phony. The self value defaults to the stub itself:

$stub = stub();

echo $stub->self() === $stub ? 'true' : 'false'; // outputs 'true'

The self value can also be set manually by calling setSelf():

$stub = stub();
$stub->setSelf('a');

echo $stub->self(); // outputs 'a'

The setSelf() method is also fluent, meaning that stub creation and self value setting can be done in a single expression:

$stub = stub()->setSelf('a');

When stubs are retrieved from a mock, their "self" value is automatically set to the mock itself:

$handle = mock(ClassA::class);
$mock = $handle->get();
$stub = $handle->methodA;

echo $stub->self() === $mock ? 'true' : 'false'; // outputs 'true'

Magic "self" values

A stubbed callback that has a first parameter named $phonySelf, regardless of the parameter's type, will receive the stub self value as the first argument.

In the case of stubs created outside of a mock, this self value will be the stub itself, allowing recursive calls without creating an explicit reference to the callback:

$factorial = stub(
    function ($phonySelf, $n) {
        if (0 === $n) {
            return 1;
        }

        return $n * $phonySelf($n - 1);
    }
);
$factorial->forwards();

echo $factorial(0); // outputs '1'
echo $factorial(1); // outputs '1'
echo $factorial(2); // outputs '2'
echo $factorial(3); // outputs '6'
echo $factorial(4); // outputs '24'
echo $factorial(5); // outputs '120'

Stub rules and answers

Stub rules define the circumstances under which a stub changes its behavior. Stub answers define how the stub behaves for a given rule. Each time a stub is called, it will chose an answer by determining which rule matches the incoming arguments:

$stub
    ->with('a')     // starts the rule
    ->returns('x'); // creates an answer for the rule

echo $stub('a'); // outputs 'x'

Multiple rules

Each time with() is called (not to be confused with calledWith(), which is part of the spy API), a new rule is started:

$stub
    ->with('a') // starts a rule
    ->returns('x')
    ->with('b') // starts another rule
    ->returns('y');

echo $stub('a'); // outputs 'x'
echo $stub('b'); // outputs 'y'

Multiple answers

A rule can have multiple answers. Each time a stub is called, Phony finds a matching rule, and uses the next answer for that rule. When the last answer is reached, Phony will continue to use it for any subsequent calls:

$stub
    ->with('a')
    ->returns('x')  // creates the first answer
    ->returns('y'); // creates a subsequent answer

echo $stub('a'); // outputs 'x'

while (true) {
  echo $stub('a'); // outputs 'y' forever
}

Overriding rules

Defining a rule that matches the same arguments as a previous rule will override the previous rule:

$stub
    ->with('a')->returns('x')
    ->with('a')->returns('y'); // overrides previous rule

echo $stub('a'); // outputs 'y'

Defining a new rule that matches any arguments will override all previous rules:

$stub
    ->with('a')->returns('x')
    ->with('b')->returns('y')
    ->with('*')->returns('z'); // overrides all previous rules

echo $stub('a'); // outputs 'z'
echo $stub('b'); // outputs 'z'

Later rules take precedence over earlier ones, so more generic rules should be defined first, with more specific rules being defined later:

$stub
    ->with('*')->returns('x')  // a generic rule
    ->with('a')->returns('y'); // a more specific rule

echo $stub('a', 'b'); // outputs 'x'
echo $stub('a');      // outputs 'y'

The default rule and answer

When a new stub is created, the first rule is started implicitly, as if with('*') were called. For example, the two following stubs behave the same:

$stubA = stub()
    ->with('*')
    ->returns('x');

$stubB = stub()
    // implicit ->with('*')
    ->returns('x');

If a new rule is started before any answers are defined, the stub behaves as if returns() were called with no arguments, causing the stub to return an "empty" value by default. For example, the two following stubs behave the same:

$stubA = stub($callable)
    ->with('*')->returns()
    ->with('a')->returns('x');

$stubB = stub($callable)
    // implicit ->with('*')->returns()
    ->with('a')->returns('x');
The default answer callback

To change the default behavior of a stub, use setDefaultAnswerCallback(). This method accepts a callback that takes the stub as the first argument:

$stub = stub();
$stub->setDefaultAnswerCallback(
    function ($stub) {
        // custom answer logic goes here
        $stub->returns('default');
    }
);

$stub->with('a')->returns('x');

echo $stub('a'); // outputs 'x'
echo $stub('b'); // outputs 'default'

The setDefaultAnswerCallback() method is also fluent, meaning stub creation and setting of this option can be done in a single expression:

$stub = stub()->setDefaultAnswerCallback($defaultAnswerCallback);

Matching stub arguments

Stub arguments can be matched using with() (not to be confused with calledWith(), which is part of the spy API). Arguments passed to with() can be literal values, or matchers, including shorthand matchers:

$stub
    ->with('*')->returns('v')
    ->with('a', '*')->returns('w')
    ->with('a', '~')->returns('x')
    ->with('a', 'b')->returns('y')
    ->with()->returns('z');

echo $stub('a');           // outputs 'v'
echo $stub('a', 'b', 'c'); // outputs 'w'
echo $stub('a', 'c');      // outputs 'x'
echo $stub('a', 'b');      // outputs 'y'
echo $stub();              // outputs 'z'

Returning values

To return a value from a stub, use returns():

$stubA = stub()->returns('x');
$stubB = stub()->returns();

echo $stubA();          // outputs 'x'
echo gettype($stubB()); // outputs 'NULL'

Calling returns() with multiple arguments is equivalent to calling it once with each argument. For example, the two following stubs behave the same:

$stubA = stub()->returns('x', 'y');

echo $stubA(); // outputs 'x'
echo $stubA(); // outputs 'y'

$stubB = stub()->returns('x')->returns('y');

echo $stubB(); // outputs 'x'
echo $stubB(); // outputs 'y'

Default values for return types

When using returns() without passing an explicit value, Phony will attempt to return a value that conforms to the stubbed callable's return type:

$stub = stub(
    function (): int {}
);
$stub->returns();

var_dump($stubA()); // outputs 'int(0)'

See emptyValue() for a full list of types and associated "empty" values.

When using a specific class name as a return type, the return value will be a mock of the specified type:

$stub = stub(function (): DateTime {})->returns();
$result = $stub();

echo $result instanceof DateTime ? 'true' : 'false'; // outputs 'true'

By necessity, the returned value will not be wrapped in a mock handle.

Returning arguments

To return an argument from a stub, use returnsArgument():

$stubA = stub()->returnsArgument();   // returns the first argument
$stubB = stub()->returnsArgument(1);  // returns the second argument
$stubC = stub()->returnsArgument(-1); // returns the last argument

echo $stubA('x', 'y', 'z'); // outputs 'x'
echo $stubB('x', 'y', 'z'); // outputs 'y'
echo $stubC('x', 'y', 'z'); // outputs 'z'

Returning the "self" value

When stubs are retrieved from a mock, their self value is automatically set to the mock itself. This allows mocking of fluent interfaces with the returnsSelf() method:

interface Fluent
{
    public function methodA();
    public function methodB();
}

$handle = mock(Fluent::class)
$handle->methodA->returnsSelf();
$handle->methodB->returns('x');

$fluent = $handle->get();

echo $fluent->methodA()->methodB(); // outputs 'x'

Throwing exceptions

To throw an exception from a stub, use throws():

$exception = new RuntimeException('You done goofed.');

$stubA = stub()->throws($exception);
$stubB = stub()->throws();

$stubA(); // throws $exception
$stubB(); // throws a generic exception

Calling throws() with multiple arguments is equivalent to calling it once with each argument. For example, the two following stubs behave the same:

$exceptionA = new RuntimeException('You done goofed.');
$exceptionB = new RuntimeException('Consequences will never be the same.');

$stubA = stub()->throws($exceptionA, $exceptionB);

$stubA(); // throws $exceptionA
$stubA(); // throws $exceptionB

$stubB = stub()->throws($exceptionA)->throws($exceptionB);

$stubB(); // throws $exceptionA
$stubB(); // throws $exceptionB

Using a callable as an answer

To use a callable as an answer, use does():

$stub = stub()->does('max');

echo $stub(2, 3, 1); // outputs '3'

Calling does() with multiple arguments is equivalent to calling it once with each argument. For example, the two following stubs behave the same:

$stubA = stub()->does('min', 'max');

echo $stubA(2, 3, 1); // outputs '1'
echo $stubA(2, 3, 1); // outputs '3'

$stubB = stub()->does('min')->does('max');

echo $stubB(2, 3, 1); // outputs '1'
echo $stubB(2, 3, 1); // outputs '3'

There is also a more powerful version of does(), named doesWith(), that allows more control over which arguments are passed to the callable, and how they are passed:

$stub = stub()->doesWith(
    'implode', // callable
    [', '],    // fixed arguments
    false,     // prefix the "self" value?
    true,      // suffix the arguments object?
    false      // suffix the arguments normally?
);

echo $stub('x', 'y', 'z'); // outputs 'x, y, z'

The doesWith() method also supports arguments passed by reference:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub()->doesWith(
    function (&$a, &$b, &$c, &$d) {
        list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
    },
    [&$a, &$b],
    false,
    false,
    true
);

$stub->invokeWith([&$c, &$d]);

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Both does() and doesWith() support magic "self" values:

$stub = stub()->does(
    function ($phonySelf, $argument) {
        // $phonySelf is the "self" value, $argument is the first argument
    }
);

Forwarding to the original callable

When stubbing an existing callable, the stub can "forward" calls on to the original callable using forwards():

$stub = stub('max')
    ->returns(9)
    ->with(2, 3, 1)->forwards();

echo $stub(2, 3, 1); // outputs '3'
echo $stub(3, 4, 5); // outputs '9'
echo $stub(7, 6, 5); // outputs '9'

This technique can be used to return a stub or mocked method to its default behavior in specific circumstances:

class Cat
{
    public function speak()
    {
        return 'Cower in fear, mortal.';
    }
}

$handle = mock(Cat::class);
$handle->speak->returns('Meow.');
$handle->speak(true)->forwards();

$cat = $handle->get();

echo $cat->speak();     // outputs 'Meow.'
echo $cat->speak(true); // outputs 'Cower in fear, mortal.'

The forwards() method also supports advanced usage, including the ability to add to, and/or remove from, the arguments passed on to the original callable:

$stub = stub('implode')->forwards(
    [', '],    // fixed arguments
    false,     // prefix the "self" value?
    true,      // suffix the arguments object?
    false      // suffix the arguments normally?
);

echo $stub('x', 'y', 'z'); // outputs 'x, y, z'

Arguments passed by reference are also supported:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub(
    function (&$a, &$b, &$c, &$d) {
        list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
    }
);

$stub->forwards([&$a, &$b], false, false, true);

$stub->invokeWith([&$c, &$d]);

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Answers that perform multiple actions

Stubs can perform multiple actions as part of a single answer. This allows callables that have side effects other than return values or exceptions to be emulated.

The most familiar of these side effects is probably the modification of passed-by-reference arguments, and the invocation of other callables, such as in an event emitter implementation.

Setting passed-by-reference arguments

To set a reference argument as part of an answer, use setsArgument():

$stub = stub(function (&$a, &$b, &$c) {})
    ->setsArgument(0, 'x')  // sets the first argument to 'x'
    ->setsArgument(1, 'y')  // sets the second argument to 'y'
    ->setsArgument(-1, 'z') // sets the last argument to 'z'
    ->returns();

$a = null;
$b = null;
$c = null;
$stub->invokeWith([&$a, &$b, &$c]);

echo $a; // outputs 'x'
echo $b; // outputs 'y'
echo $c; // outputs 'z'

If only one argument is passed to setsArgument(), it sets the first argument:

$stub = stub(function (&$a) {})
    ->setsArgument('x') // sets the first argument to 'x'
    ->returns();

$a = null;
$stub->invokeWith([&$a]);

echo $a; // outputs 'x'

If setsArgument() is called without any arguments, it sets the first argument to null:

$stub = stub(function (&$a) {})
    ->setsArgument() // sets the first argument to `null`
    ->returns();

$a = 'x';
$stub->invokeWith([&$a]);

echo gettype($a); // outputs 'NULL'

Invoking arguments

To invoke an argument as part of an answer, use callsArgument():

$stub = stub()
    ->callsArgument()   // calls the first argument
    ->callsArgument(1)  // calls the second argument
    ->callsArgument(-1) // calls the last argument
    ->returns();

$x = function () { echo 'x'; };
$y = function () { echo 'y'; };
$z = function () { echo 'z'; };

$stub($x, $y, $z); // outputs 'xyz'

There is also a more powerful version of callsArgument(), named callsArgumentWith(), that allows more control over which arguments are passed to the callable, and how they are passed:

$stub = stub()
    ->callsArgumentWith(
        1,              // argument to invoke
        ['%s, %s, %s'], // fixed arguments
        false,          // prefix the "self" value?
        true,           // suffix the arguments object?
        false           // suffix the arguments normally?
    )
    ->returns();

$stub('x', 'printf', 'y'); // outputs 'x, printf, y'

The callsArgumentWith() method also supports arguments passed by reference:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub()->callsArgumentWith(
    -1,
    [&$a, &$b],
    false,
    false,
    true
);

$callback = function (&$a, &$b, &$c, &$d) {
    list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
};

$stub->invokeWith([&$c, &$d, $callback]);

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Invoking callables

To invoke a callable as part of an answer, use calls():

$stub = stub()->calls('printf')->returns();

$stub('%s, %s', 'a', 'b'); // outputs 'a, b'

Calling calls() with multiple arguments is equivalent to calling it once with each argument. For example, the two following stubs behave the same:

$x = function () { echo 'x'; };
$y = function () { echo 'y'; };

$stubA = stub()->calls($x, $y)->returns();

$stubA(); // outputs 'xy'

$stubB = stub()->calls($x)->calls($y)->returns();

$stubB(); // outputs 'xy'

There is also a more powerful version of calls(), named callsWith(), that allows more control over which arguments are passed to the callable, and how they are passed:

$stub = stub()
    ->callsWith(
        'printf',   // argument to invoke
        ['%s, %s'], // fixed arguments
        false,      // prefix the "self" value?
        false,      // suffix the arguments object?
        true        // suffix the arguments normally?
    )
    ->returns();

$stub('x', 'y'); // outputs 'x, y'

The callsWith() method also supports arguments passed by reference:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub()->callsWith(
    function (&$a, &$b, &$c, &$d) {
        list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
    },
    [&$a, &$b],
    false,
    false,
    true
);

$stub->invokeWith([&$c, &$d]);

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Both calls() and callsWith() support magic "self" values:

$stub = stub()->calls(
    function ($phonySelf, $argument) {
        // $phonySelf is the "self" value, $argument is the first argument
    }
);

Stubbing generators

To return a generator from a stub, use generates():

$stub = stub();
$stub->generates();

$generator = $stub();
$values = iterator_to_array($generator); // consume the generator

echo $generator instanceof Generator ? 'true' : 'false'; // outputs 'true'
echo json_encode($values);                               // outputs '[]'

The result of generates() is a generator answer. This object can be used to further customize the behavior of the generator. See the subsequent headings for details of these customizations.

Certain methods, such as returns(), or throws(), mark the "end" of generator answer. When a generator answer is "ended", the original stub is returned, allowing continued stubbing in a fluent manner:

$stub = stub()
    ->generates()     // returns a generator
        ->yields('a')
        ->yields('b')
        ->returns()   // ends the generator
    ->returns('c')    // returns a normal value
    ->generates()     // returns another generator
        ->yields('d')
        ->yields('e')
        ->throws();   // ends the generator by throwing

$resultA = $stub();
$resultB = $stub();
$resultC = $stub();

echo $resultA instanceof Generator ? 'true' : 'false'; // outputs 'true'
echo $resultB instanceof Generator ? 'true' : 'false'; // outputs 'false'
echo $resultC instanceof Generator ? 'true' : 'false'; // outputs 'true'

Yielding from a generator

Keys and values to be yielded can be passed directly to generates() as any iterable value:

$stub = stub()
    ->generates(['a', 'b', 'c', 'd'])->returns()
    ->generates(['e' => 'f', 'g' => 'h'])->returns();

$generatorA = $stub();
$generatorB = $stub();

$valuesA = iterator_to_array($generatorA); // consume the generator
$valuesB = iterator_to_array($generatorB); // consume the generator

echo json_encode($valuesA); // outputs '["a","b","c","d"]'
echo json_encode($valuesB); // outputs '{"e":"f","g":"h"}'
Yielding individual values from a generator

For more complicated generator behavior stubbing, yields() can be used to interleave yields with other actions:

$count = 0;
$callback = function () use (&$count) {
    printf("Called %d time(s)\n", ++$count);
};

$stub = stub(function (&$argument) {});
$stub->generates()
    ->calls($callback)
    ->yields('a')
    ->calls($callback)
    ->yields('b');

foreach ($stub() as $value) {
    printf("Value: %s\n", $value);
}

The above example outputs:

Called 1 time(s)
Value: a
Called 2 time(s)
Value: b

If yields() is called with 2 arguments, they are treated as key and value respectively. When called with 1 argument, the argument is treated as the value to yield. When called with no arguments, the generator will yield with no value:

$stub = stub()->generates()
    ->yields('a', 'b')
    ->yields('c')
    ->yields()
    ->returns();

$values = iterator_to_array($stub()); // consume the generator

echo json_encode($values); // outputs '{"a":"b","0":"c","1":null}'
Yielding multiple values from a generator

To yield a set of values from an array, an iterator, or another generator, use yieldsFrom():

$stub = stub()->generates()
    ->yieldsFrom(['a' => 'b', 'c' => 'd'])
    ->yieldsFrom(['e' => 'f'])
    ->returns();

$values = iterator_to_array($stub()); // consume the generator

echo json_encode($values); // outputs '{"a":"b","c":"d","e":"f"}'

Calling generates() with multiple sets of values creates multiple answers that yield the supplied values on subsequent invocations. The returned generator answer will allow customization of the final answer only. For example, the two following stubs behave the same:

$stubA = stub()
    ->generates(['a', 'b'], ['c', 'd'])
    ->yields('e')
    ->returns();

echo json_encode(iterator_to_array($stubA())); // outputs '["a","b"]'
echo json_encode(iterator_to_array($stubA())); // outputs '["c","d","e"]'

$stubB = stub()
    ->generates(['a', 'b'])
    ->returns()
    ->generates(['c', 'd'])
    ->yields('e')
    ->returns();

echo json_encode(iterator_to_array($stubB())); // outputs '["a","b"]'
echo json_encode(iterator_to_array($stubB())); // outputs '["c","d","e"]'

Returning values from a generator

To return a value from a generator, use returns() on any generator answer:

$stub = stub()->generates()->returns('a');

$generator = $stub();
iterator_to_array($generator); // consume the generator

echo $generator->getReturn(); // outputs 'a'

Calling returns() with multiple arguments allows for easy specification of the generator return value on subsequent invocations. For example, the two following stubs behave the same:

$stubA = stub()
    ->generates()->returns('x', 'y');

$generatorA = $stubA();
$generatorB = $stubA();
iterator_to_array($generatorA);
iterator_to_array($generatorB);

echo $generatorA->getReturn(); // outputs 'x'
echo $generatorB->getReturn(); // outputs 'y'

$stubB = stub()
    ->generates()->returns('x')
    ->generates()->returns('y');

$generatorA = $stubB();
$generatorB = $stubB();
iterator_to_array($generatorA);
iterator_to_array($generatorB);

echo $generatorA->getReturn(); // outputs 'x'
echo $generatorB->getReturn(); // outputs 'y'

Note that it is perfectly valid to call returns() with no arguments in order to end the generator by returning null:

$stub = stub()->generates()->returns();

Returning arguments from a generator

To return an argument from a generator, use returnsArgument() on any generator answer:

$stubA = stub()->generates()->returnsArgument();   // returns the first argument
$stubB = stub()->generates()->returnsArgument(1);  // returns the second argument
$stubC = stub()->generates()->returnsArgument(-1); // returns the last argument

$generatorA = $stubA('x', 'y', 'z');
$generatorB = $stubB('x', 'y', 'z');
$generatorC = $stubC('x', 'y', 'z');

iterator_to_array($generatorA); // consume the generator
iterator_to_array($generatorB); // consume the generator
iterator_to_array($generatorC); // consume the generator

echo $generatorA->getReturn(); // outputs 'x'
echo $generatorB->getReturn(); // outputs 'y'
echo $generatorC->getReturn(); // outputs 'z'

Returning the "self" value from a generator

The stub self value can be returned from a generator by using returnsSelf() on any generator answer:

$handle = mock();
$handle->methodA->generates()->returnsSelf();

$mock = $handle->get();
$generator = $mock->methodA();

iterator_to_array($generator); // consume the generator

echo $generator->getReturn() === $mock ? 'true' : 'false'; // outputs 'true'

Throwing exceptions from a generator

To throw an exception from a generator, use throws() on any generator answer:

$exception = new RuntimeException('You done goofed.');

$stubA = stub()->generates()->throws($exception);
$stubB = stub()->generates()->throws();

$generatorA = $stubA();
$generatorB = $stubB();

iterator_to_array($generatorA); // throws $exception
iterator_to_array($generatorB); // throws a generic exception

Calling throws() with multiple arguments allows for easy specification of the thrown exception on subsequent invocations. For example, the two following stubs behave the same:

$exceptionA = new RuntimeException('You done goofed.');
$exceptionB = new RuntimeException('Consequences will never be the same.');

$stubA = stub()
    ->generates()->throws($exceptionA, $exceptionB);

$generatorA = $stubA();
$generatorB = $stubA();

iterator_to_array($generatorA); // throws $exceptionA
iterator_to_array($generatorB); // throws $exceptionB

$stubB = stub()
    ->generates()->throws($exceptionA)
    ->generates()->throws($exceptionB);

$generatorA = $stubB();
$generatorB = $stubB();

iterator_to_array($generatorA); // throws $exceptionA
iterator_to_array($generatorB); // throws $exceptionB

Generator iterations that perform multiple actions

Stubbed generators can perform multiple actions as part of a single iteration. This allows side effects other than yielded values to be emulated.

The most familiar of these side effects is probably the modification of passed-by-reference arguments, and the invocation of other callables.

Setting passed-by-reference arguments in a generator

To set a reference argument as part of a generator, use setsArgument() on any generator answer:

$stub = stub(function (&$a, &$b, &$c) {})->generates()
    ->setsArgument(0, 'x')  // sets the first argument to 'x'
    ->setsArgument(1, 'y')  // sets the second argument to 'y'
    ->setsArgument(-1, 'z') // sets the last argument to 'z'
    ->returns();

$a = null;
$b = null;
$c = null;
$generator = $stub->invokeWith([&$a, &$b, &$c]);
iterator_to_array($generator); // consume the generator

echo $a; // outputs 'x'
echo $b; // outputs 'y'
echo $c; // outputs 'z'

If only one argument is passed to setsArgument(), it sets the first argument:

$stub = stub(function (&$a) {})->generates()
    ->setsArgument('x') // sets the first argument to 'x'
    ->returns();

$a = null;
$generator = $stub->invokeWith([&$a]);
iterator_to_array($generator); // consume the generator

echo $a; // outputs 'x'

If setsArgument() is called without any arguments, it sets the first argument to null:

$stub = stub(function (&$a) {})->generates()
    ->setsArgument() // sets the first argument to `null`
    ->returns();

$a = 'x';
$generator = $stub->invokeWith([&$a]);
iterator_to_array($generator); // consume the generator

echo gettype($a); // outputs 'NULL'

Setting of arguments can be configured to occur in between yields:

$stub = stub(function (&$a) {})->generates()
    ->setsArgument('x') // first iteration starts
    ->yields('a')       // first iteration ends
    ->setsArgument('y') // second iteration starts
    ->yields('b')       // second iteration ends
    ->returns();

$a = null;

foreach ($stub->invokeWith([&$a]) as $value) {
    printf("%s, %s\n", $value, $a);
}

The above example outputs:

a, x
b, y
Invoking arguments in a generator

To invoke an argument as part of a generator, use callsArgument() on any generator answer:

$stub = stub()->generates()
    ->callsArgument()   // calls the first argument
    ->callsArgument(1)  // calls the second argument
    ->callsArgument(-1) // calls the last argument
    ->returns();

$x = function () { echo 'x'; };
$y = function () { echo 'y'; };
$z = function () { echo 'z'; };

$generator = $stub($x, $y, $z);
iterator_to_array($generator); // outputs 'xyz'

There is also a more powerful version of callsArgument(), named callsArgumentWith(), that allows more control over which arguments are passed to the callable, and how they are passed:

$stub = stub()->generates()
    ->callsArgumentWith(
        1,              // argument to invoke
        ['%s, %s, %s'], // fixed arguments
        false,          // prefix the "self" value?
        true,           // suffix the arguments object?
        false           // suffix the arguments normally?
    )
    ->returns();

$generator = $stub('x', 'printf', 'y');
iterator_to_array($generator); // outputs 'x, printf, y'

The callsArgumentWith() method also supports arguments passed by reference:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub()->generates()
    ->callsArgumentWith(
        -1,
        [&$a, &$b],
        false,
        false,
        true
    )
    ->returns();

$callback = function (&$a, &$b, &$c, &$d) {
    list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
};

$generator = $stub->invokeWith([&$c, &$d, $callback]);
iterator_to_array($generator); // consume the generator

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Calling of arguments can be configured to occur in between yields:

$count = 0;
$callback = function () use (&$count) {
    printf("Called %d time(s)\n", ++$count);
};

$stub = stub(function (&$a) {})->generates()
    ->callsArgument() // first iteration starts
    ->yields('a')     // first iteration ends
    ->callsArgument() // second iteration starts
    ->yields('b')     // second iteration ends
    ->returns();

foreach ($stub($callback) as $value) {
    printf("Value: %s\n", $value);
}

The above example outputs:

Called 1 time(s)
Value: a
Called 2 time(s)
Value: b
Invoking callables in a generator

To invoke a callable as part of a generator, use calls() on any generator answer:

$stub = stub()->generates()->calls('printf')->returns();

$generator = $stub('%s, %s', 'a', 'b');
iterator_to_array($generator); // outputs 'a, b'

Calling calls() with multiple arguments is equivalent to calling it once with each argument. For example, the two following stubs behave the same:

$x = function () { echo 'x'; };
$y = function () { echo 'y'; };

$stubA = stub()->generates()->calls($x, $y)->returns();

$generator = $stubA();
iterator_to_array($generator); // outputs 'xy'

$stubB = stub()->generates()->calls($x)->calls($y)->returns();

$generator = $stubB();
iterator_to_array($generator); // outputs 'xy'

There is also a more powerful version of calls(), named callsWith(), that allows more control over which arguments are passed to the callable, and how they are passed:

$stub = stub()->generates()
    ->callsWith(
        'printf',   // argument to invoke
        ['%s, %s'], // fixed arguments
        false,      // prefix the "self" value?
        false,      // suffix the arguments object?
        true        // suffix the arguments normally?
    )
    ->returns();

$generator = $stub('x', 'y');
iterator_to_array($generator); // outputs 'x, y'

The callsWith() method also supports arguments passed by reference:

$a = null;
$b = null;
$c = null;
$d = null;

$stub = stub()->generates()
    ->callsWith(
        function (&$a, &$b, &$c, &$d) {
            list($a, $b, $c, $d) = ['a', 'b', 'c', 'd'];
        },
        [&$a, &$b],
        false,
        false,
        true
    )
    ->returns();

$generator = $stub->invokeWith([&$c, &$d])
iterator_to_array($generator); // consume the generator

echo $a; // outputs 'a'
echo $b; // outputs 'b'
echo $c; // outputs 'c'
echo $d; // outputs 'd'

Calling of callables can be configured to occur in between yields:

$count = 0;
$callback = function () use (&$count) {
    printf("Called %d time(s)\n", ++$count);
};

$stub = stub(function (&$a) {})->generates()
    ->calls($callback) // first iteration starts
    ->yields('a')      // first iteration ends
    ->calls($callback) // second iteration starts
    ->yields('b')      // second iteration ends
    ->returns();

foreach ($stub($callback) as $value) {
    printf("Value: %s\n", $value);
}

The above example outputs:

Called 1 time(s)
Value: a
Called 2 time(s)
Value: b

Spies

Spies record interactions with callable entities, such as functions, methods, closures, and objects with an __invoke() method. They can be used to verify both the input, and output of function calls.

Most of the methods in the spy API are mirrored in the call API.

Spying on an existing callable

Any callable can be wrapped in a spy, by passing the callable to spy():

$spy = spy($callable);        // with `use function`
$spy = Phony::spy($callable); // without `use function`

The created spy will behave exactly like the wrapped callable:

$spy = spy('max');

echo $spy(2, 3, 1); // outputs '3'

Spying on global functions

When an "unqualified" function (one with no preceding backslash) is used from within a namespace, PHP will attempt to find the function in the calling namespace first, before looking for the function in the global namespace. This behavior is known as global function fallback.

This behavior can be exploited to allow a spy to record interactions with a global function during testing. To do so, two conditions must be met:

To spy on a function in the global namespace, use spyGlobal():

$spy = spyGlobal($function, $namespace);        // with `use function`
$spy = Phony::spyGlobal($function, $namespace); // without `use function`

Where $function is the name of the function in the global namespace, and $namespace is the namespace from which the function will be called.

To demonstrate, the following code shows that if the sprintf() function is spied on before it is called, its input and output can be verified:

namespace Foo\Bar;
use function Eloquent\Phony\spyGlobal;

$spy = spyGlobal('sprintf', __NAMESPACE__);

$message = sprintf('Keep it %s.', 'real');

$spy->calledWith('Keep it %s.', 'real'); // verification passes
$spy->returned('Keep it real.');         // verification passes
Restoring global functions after spying

Leaving global functions spied after a test can result in additional memory use, as all further interactions with the function will be recorded by Phony. To avoid this problem, it is necessary to restore the behavior of spied global functions after the test concludes.

To restore all spied and/or stubbed global functions, use restoreGlobalFunctions():

restoreGlobalFunctions();        // with `use function`
Phony::restoreGlobalFunctions(); // without `use function`

This method is suitable for use in the "tear down" phase of a test.

Alternatives for spying on global functions

If the system under test is not suitable for spying via global function fallback, an alternative is to accept a callback via dependency injection that can take a spy during testing:

use function Eloquent\Phony\spy;

function functionA($sprintf = 'sprintf')
{
    $message = $sprintf('Keep it %s.', 'real');
}

$spy = spy('sprintf');

functionA($spy);

$spy->calledWith('Keep it %s.', 'real'); // verification passes
$spy->returned('Keep it real.');         // verification passes

Another alternative is to use a library like Isolator, that allows the injection of an explicit dependency that represents the functions in the global namespace. This dependency can then be mocked during testing.

Anonymous spies

Anonymous spies are spies that do not wrap an existing callable. Their only purpose is to record input arguments. An anonymous spy is created by calling spy() without passing a callable:

$spy = spy();        // with `use function`
$spy = Phony::spy(); // without `use function`

Regardless of input arguments, anonymous spies will always return null:

$spy = spy();

echo gettype($spy('a')); // outputs 'NULL'

Call verification

Phony provides the ability to make verifications on individual recorded calls. The API for verifying calls mirrors the methods available for spy verification.

See the call API for more information.

Call count

The number of calls recorded by a spy can be retrieved using callCount():

$spy->callCount();

Individual calls

To get the first call, use firstCall():

$spy->firstCall();

To get the last call, use lastCall():

$spy->lastCall();

To get a specific call by index, use callAt():

$spy->callAt(0); // returns the first call
$spy->callAt(9); // returns the tenth call

These methods will throw an exception if no call is found.

Similar methods also exist for verification results:

$spy->called()->firstCall();
$spy->called()->lastCall();
$spy->called()->callAt(0);

Verifying spy input

Verifying that a call was made

To verify that a spy was called, use called():

$spy->called();

Example output from called():

Example output from $spy->called()

Verifying that a spy was called with specific arguments

To verify input arguments, use calledWith(). Arguments passed to calledWith() can be literal values, or matchers, including shorthand matchers:

$spy->calledWith();         // called with no arguments
$spy->calledWith('a', 'b'); // called with 'a' followed by 'b'
$spy->calledWith('a', '*'); // called with 'a' followed by 0-n arguments
$spy->calledWith('a', '~'); // called with 'a' followed by exactly 1 argument

Arguments can be retrieved by calling arguments() or argument() on an individual call recorded via the spy:

$spy->firstCall()->arguments(); // all arguments as an array
$spy->firstCall()->argument();  // first argument
$spy->firstCall()->argument(1); // second argument

Calls can also be retrieved from any verification result:

$spy->called()->firstCall()->arguments(); // all arguments as an array
$spy->called()->firstCall()->argument();  // first argument
$spy->called()->firstCall()->argument(1); // second argument

Example output from calledWith():

Example output from $spy->calledWith()

Verifying spy output

Verifying spy return values

To verify a spy's return value, use returned():

$spy->returned();    // returned anything
$spy->returned('a'); // returned 'a'

Return values can be retrieved by calling returnValue() on an individual call recorded via the spy:

$value = $spy->firstCall()->returnValue();

Calls can also be retrieved from any verification result:

$value = $spy->called()->firstCall()->returnValue();

Example output from returned():

Example output from $spy->returned()

Verifying generators returned by spies

To verify that a spy returned a generator, use generated():

$spy->generated();

The result returned by generated() can be used for further verification of the generator's behavior:

$generator = $spy->generated(); // returned a generator

$generator->produced('a'); // generator yielded 'a'
$generator->returned('b'); // generator returned 'b'

See Generator and iterable verification for a complete explanation of the available verifications.

Example output from generated():

Example output from $spy->generated()

Verifying iterables returned by spies

To verify that a spy returned an iterable value, such as an array or iterator, use iterated():

$spy->iterated();

If iterable spies are enabled, the result returned by iterated() can be used for further verification of the iterable's behavior:

$iterable = $spy->iterated(); // returned an iterable

$iterable->produced('a');      // iterable produced 'a'
$iterable->produced('b', 'c'); // iterable produced 'b' => 'c'

See Generator and iterable verification for a complete explanation of the available verifications.

Example output from iterated():

Example output from $spy->iterated()

Verifying spy exceptions

To verify that a spy threw an exception, use threw():

$spy->threw();                                         // threw any exception
$spy->threw('RuntimeException');                       // threw a runtime exception
$spy->threw(new RuntimeException('You done goofed.')); // threw a runtime exception with a specific message

Thrown exceptions can be retrieved by calling exception() on an individual call recorded via the spy:

$exception = $spy->firstCall()->exception();

Calls can also be retrieved from any verification result:

$exception = $spy->called()->firstCall()->exception();

Example output from threw():

Example output from $spy->threw()

Verifying spy progress

To verify that a spy call has completed, use completed():

$spy->completed();

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

To ignore iterable events, and simply verify that a spy has returned a value or thrown an exception, use responded():

$spy->responded();

Responses can be retrieved by calling response() on an individual call recorded via the spy:

list($exception, $value) = $spy->firstCall()->response();

Generator responses can be retrieved by calling generatorResponse() on an individual call recorded via the spy:

list($exception, $value) = $spy->firstCall()->generatorResponse();

Calls can also be retrieved from any verification result:

list($exception, $value) = $spy->called()->firstCall()->response();
list($exception, $value) = $spy->called()->firstCall()->generatorResponse();

Example output from completed():

Example output from $spy->completed()

Example output from responded():

Example output from $spy->responded()

Verifying cardinality with spies

When used with a spy, cardinality modifiers change the amount of calls that must meet the requirements of a subsequent verification.

This differs slightly from their usage with calls, where cardinality modifiers change the amount of events within the call that must meet the requirements of a subsequent verification.

Note that cardinality also applies differently when using generator and iterable verification. See Verifying cardinality with generators and iterables.

Cardinality must be specified before verification, and can be applied to any verification:

$spy->once()->called();        // called exactly 1 time
$spy->once()->calledWith('a'); // called exactly 1 time with 'a'
$spy->once()->returned('b');   // returned 'b' exactly 1 time

The default cardinality is atLeast(1), meaning verifications will pass if at least one matching event was recorded.

Verifying that a spy event happened an exact number of times

To verify that an event happened an exact number of times, use one of never(), once(), twice(), thrice(), or times():

$spy->never()->called();   // never called
$spy->once()->called();    // called exactly 1 time
$spy->twice()->called();   // called exactly 2 times
$spy->thrice()->called();  // called exactly 3 times
$spy->times(10)->called(); // called exactly 10 times

$spy->never()->returned('a');   // never returned 'a'
$spy->once()->returned('a');    // returned 'a' exactly 1 time
$spy->twice()->returned('a');   // returned 'a' exactly 2 times
$spy->thrice()->returned('a');  // returned 'a' exactly 3 times
$spy->times(10)->returned('a'); // returned 'a' exactly 10 times

Verifying that a spy event happened a bounded number of times

To verify that an event happened a bounded number of times, use one of atLeast(), atMost(), or between():

$spy->atLeast(2)->called();    // called 2 or more times
$spy->atMost(3)->called();     // called no more than 3 times
$spy->between(2, 4)->called(); // called 2, 3, or 4 times

$spy->atLeast(2)->returned('a');    // returned 'a' 2 or more times
$spy->atMost(3)->returned('a');     // returned 'a' no more than 3 times
$spy->between(2, 4)->returned('a'); // returned 'a' 2, 3, or 4 times

Verifying that all spy events happen the same way

To verify that all events happen the same way, use always():

$spy->always()->calledWith('a'); // always called with 'a'
$spy->always()->returned('b');   // always returned 'b'

Note that always() does not interfere with other cardinality modifiers, and can be combined to produce powerful verifications:

$spy->twice()->always()->calledWith('a'); // called exactly 2 times, and always with 'a'

Labeling spies

Every spy has a label, which is a free-form string used to help identify the spy in verification failure messages. By default, each spy is assigned a unique sequential integer label upon creation.

The label can be changed at any time by using setLabel():

$spy = spy();
$spy->setLabel('a');

echo $spy->label(); // outputs 'a'

The setLabel() method is also fluent, meaning that spy creation and label setting can be done in a single expression:

$spy = spy()->setLabel('a');

When a verification fails for a labeled spy, the output is similar to the following:

Example verification output when using a spy label

Invoking spies

Spies can be invoked directly like any other dynamic callable:

$spy('a', 'b');

$spy->calledWith('a', 'b'); // passes

They can also be invoked more explicitly using invoke():

$spy->invoke('a', 'b');

$spy->calledWith('a', 'b'); // passes

There is also a more advanced method, invokeWith(), that supports arguments passed by reference:

$spy = spy(
    function (&$a, &$b) {
        list($a, $b) = ['x', 'y'];
    }
);

$a = 'a';
$b = 'b';

$spy->invokeWith([&$a, &$b]);

$spy->calledWith('a', 'b'); // passes

echo $a; // outputs 'x'
echo $b; // outputs 'y'

Pausing spy recording

To temporarily prevent spies from recording calls, use stopRecording() and startRecording() as necessary:

$spy = spy();
$spy('a');
$spy->stopRecording();
$spy('b');
$spy->startRecording();
$spy('c');

$spy->calledWith('a'); // passes
$spy->calledWith('c'); // passes

$spy->calledWith('b'); // fails

Calls

Phony provides the ability to make verifications on individual recorded calls. The call API mirrors the methods available on the spy API.

Retrieving calls from a spy

To get the first call, use firstCall():

$spy->firstCall();

To get the last call, use lastCall():

$spy->lastCall();

To get a specific call by index, use callAt():

$spy->callAt(0); // returns the first call
$spy->callAt(9); // returns the tenth call

These methods will throw an exception if no call is found.

Verifying call input

Verifying that a call was made with specific arguments

To verify input arguments, use calledWith(). Arguments passed to calledWith() can be literal values, or matchers, including shorthand matchers:

$call->calledWith();         // called with no arguments
$call->calledWith('a', 'b'); // called with 'a' followed by 'b'
$call->calledWith('a', '*'); // called with 'a' followed by 0-n arguments
$call->calledWith('a', '~'); // called with 'a' followed by exactly 1 argument

Arguments can also be retrieved with arguments() or argument():

$call->arguments(); // all arguments as an array
$call->argument();  // first argument
$call->argument(1); // second argument

Example output from calledWith():

Example output from $call->calledWith()

Verifying call output

Verifying call return values

To verify a call's return value, use returned():

$call->returned();    // returned anything
$call->returned('a'); // returned 'a'

Return values can be retrieved with returnValue():

$value = $call->returnValue();

Example output from returned():

Example output from $call->returned()

Verifying generators returned by calls

To verify that a call returned a generator, use generated():

$call->generated();

The result returned by generated() can be used for further verification of the generator's behavior:

$generator = $call->generated(); // returned a generator

$generator->produced('a'); // generator yielded 'a'
$generator->returned('b'); // generator returned 'b'

See Generator and iterable verification for a complete explanation of the available verifications.

Example output from generated():

Example output from $call->generated()

Verifying iterables returned by calls

To verify that a call returned an iterable value, such as an array or iterator, use iterated():

$call->iterated();

If iterable spies are enabled, the result returned by iterated() can be used for further verification of the iterable's behavior:

$iterable = $call->iterated(); // returned an iterable

$iterable->produced('a');      // iterable produced 'a'
$iterable->produced('b', 'c'); // iterable produced 'b' => 'c'

See Generator and iterable verification for a complete explanation of the available verifications.

Example output from iterated():

Example output from $call->iterated()

Verifying call exceptions

To verify that a call threw an exception, use threw():

$call->threw();                                         // threw any exception
$call->threw('RuntimeException');                       // threw a runtime exception
$call->threw(new RuntimeException('You done goofed.')); // threw a runtime exception with a specific message

Thrown exceptions can be retrieved with exception():

$exception = $call->exception();

Example output from threw():

Example output from $call->threw()

Verifying call progress

To verify that a call has completed, use completed():

$call->completed();

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

To ignore iterable events, and simply verify that a call has returned a value or thrown an exception, use responded():

$call->responded();

Responses can be retrieved with exception():

list($exception, $value) = $call->response();

Generator responses can be retrieved with exception():

list($exception, $value) = $call->generatorResponse();

Example output from completed():

Example output from $call->completed()

Example output from responded():

Example output from $call->responded()

Verifying cardinality with calls

When used with a call, cardinality modifiers change the amount of times that events within that call must meet the requirements of a subsequent verification.

This differs slightly from their usage with spies, where cardinality modifiers change the amount of calls that must meet the requirements of a subsequent verification.

Cardinality must be specified before verification, and can be applied to any verification:

$call->never()->calledWith('a'); // not called with 'a'
$call->never()->returned('b');   // did not return 'b'
$call->never()->threw();         // did not throw an exception

The default cardinality is atLeast(1), meaning verifications will pass if at least one matching event was recorded.

Verifying that a call event happened an exact number of times

To verify that an event happened an exact number of times, use one of never(), once(), twice(), thrice(), or times():

$call->never()->produced('a');   // never produced 'a'
$call->once()->produced('a');    // produced 'a' exactly 1 time
$call->twice()->produced('a');   // produced 'a' exactly 2 times
$call->thrice()->produced('a');  // produced 'a' exactly 3 times
$call->times(10)->produced('a'); // produced 'a' exactly 10 times

$call->never()->received('a');   // never received 'a'
$call->once()->received('a');    // received 'a' exactly 1 time
$call->twice()->received('a');   // received 'a' exactly 2 times
$call->thrice()->received('a');  // received 'a' exactly 3 times
$call->times(10)->received('a'); // received 'a' exactly 10 times

Verifying that a call event happened a bounded number of times

To verify that an event happened a bounded number of times, use one of atLeast(), atMost(), or between():

$call->atLeast(2)->produced('a');    // produced 'a' 2 or more times
$call->atMost(3)->produced('a');     // produced 'a' no more than 3 times
$call->between(2, 4)->produced('a'); // produced 'a' 2, 3, or 4 times

$call->atLeast(2)->received('a');    // received 'a' 2 or more times
$call->atMost(3)->received('a');     // received 'a' no more than 3 times
$call->between(2, 4)->received('a'); // received 'a' 2, 3, or 4 times

Verifying that all call events happen the same way

To verify that all events happen the same way, use always():

$call->always()->produced('a'); // always produced 'a'
$call->always()->received('b'); // always received 'b'

Note that always() does not interfere with other cardinality modifiers, and can be combined to produce powerful verifications:

$call->twice()->always()->produced('a'); // produced exactly 2 values, both of which are 'a'

Verification

"Verification" is a general term, used to refer to any Phony method or function that asserts that something happened. Phony implements many verification methods and functions across its API, but they all behave in a similar manner.

Each verification method or function has two variants. For most use-cases, the standard verification style will be the best fit. When verification fails, this variant will record an assertion failure with the testing framework in use. This typically (but not always) involves an exception being thrown.

For situations where this is not desirable, the check verification style can be used. When verification fails, this variant will simply return null, and no assertion failure will be recorded with the testing framework in use.

For information on specific verification methods, see these sections:

Standard verification

"Standard" verification involves "recording" failures with the testing framework in use. For most testing frameworks, this involves throwing a specific type of exception used by the framework.

In some cases, the framework will not use a specific exception type, and will treat any exception as a failure. In this case, and in the case that no testing framework is being used, Phony will simply throw its own AssertionException on failure.

In rare cases, failures do not result in any exception being thrown. The failure is recorded with the testing framework via whatever method the framework uses instead. This may mean that execution continues past the failed verification call.

As with check verification, verification successes will return a verification result, which can be used for further verifications, including order verification.

Check verification

In contrast to standard verification, "check" verification does not involve integration with the testing framework in use.

In the case of verification failure, null will be returned. As with standard verification, verification successes will return a verification result, which can be used for further verifications, including order verification.

Check verification may be used to integrate with testing frameworks that do not yet have first-class support in Phony:

expect($spy->checkCalled())->to->be->ok;

Understanding verification output

See also: The export format.

Typical verification output looks something like the following example. Hovering over a section of the example will provide an explanation of that part of the output:

Example verification output

Expected behavior output

The first part of the output explains what was expected. In the above example:

Cardinality output

The second part of the output explains the "cardinality" of both the expectation (if relevant), and the actual events. In the above example:

Actual behavior output

The third part of the output explains what actually happened. In the above example:

Generator and iterable verification

When spying on a callable that returns a generator, additional verification can be performed on events that happen after the generator is returned, such as the yielding of values. This is accomplished via the use of "generator spies".

The use of generator spies is an optional behavior that can be disabled by calling setUseGeneratorSpies() on a spy:

$spy->setUseGeneratorSpies(false);

"Iterable spies" also exist for other iterators and arrays, but are disabled by default because they typically change the return type of the spy. This feature can be enabled by calling setUseIterableSpies() on a spy:

$spy->setUseIterableSpies(true);

Both setUseGeneratorSpies() and setUseIterableSpies() are fluent, meaning spy creation and setting of these options can be done in a single expression:

$spy = spy()
    ->setUseIterableSpies(true)
    ->setUseGeneratorSpies(false);

Verifying iteration

To verify that iteration of a generator or iterable commenced, use used() on any generator verification result or iterable verification result:

$subject->generated()->used(); // iteration of the generator commenced
$subject->iterated()->used();  // iteration of the iterable commenced

To verify that iteration of a generator or iterable completed, use consumed() on any generator verification result or iterable verification result:

$subject->generated()->consumed(); // iteration of the generator completed
$subject->iterated()->consumed();  // iteration of the iterable completed

Example output from used():

Example output from $iterable->used()

Example output from consumed():

Example output from $iterable->consumed()

Verifying produced values

To verify that a value was yielded by a generator, use produced() on any generator verification result:

$generator = $subject->generated(); // returned a generator

$generator->produced();         // produced anything
$generator->produced('a');      // produced 'a' with any key
$generator->produced('a', 'b'); // produced 'b' with key 'a'

To verify that a value was produced by an iterable, use produced() on any iterable verification result:

$iterable = $subject->iterated(); // returned an iterable

$iterable->produced();         // produced anything
$iterable->produced('a');      // produced 'a' with any key
$iterable->produced('a', 'b'); // produced 'b' with key 'a'

Example output from produced():

Example output from $iterable->produced()

Verifying values received by generators

To verify that a value was received by a generator, use received() on any generator verification result:

$generator = $subject->generated(); // returned a generator

$generator->received();    // received anything
$generator->received('a'); // received 'a'

Example output from received():

Example output from $generator->received()

Verifying exceptions received by generators

To verify that an exception was received by a generator, use receivedException() on any generator verification result:

$generator = $subject->generated(); // returned a generator

$generator->receivedException();                                         // received any exception
$generator->receivedException('RuntimeException');                       // received a runtime exception
$generator->receivedException(new RuntimeException('You done goofed.')); // received a runtime exception with a specific message

Example output from receivedException():

Example output from $generator->receivedException()

Verifying generator return values

To verify a generator's return value, use returned() on any generator verification result:

$generator = $subject->generated(); // returned a generator

$generator->returned();    // returned anything
$generator->returned('a'); // returned 'a'

Example output from returned():

Example output from $generator->returned()

Verifying generator exceptions

To verify that a generator threw an exception, use threw() on any generator verification result:

$generator = $subject->generated(); // returned a generator

$generator->threw();                                         // threw any exception
$generator->threw('RuntimeException');                       // threw a runtime exception
$generator->threw(new RuntimeException('You done goofed.')); // threw a runtime exception with a specific message

Example output from threw():

Example output from $generator->threw()

Verifying cardinality with generators and iterables

When used before generated() or iterated(), cardinality modifiers change the amount of calls that must return a generator or iterable:

$subject->twice()->generated(); // returned a generator exactly 2 times
$subject->twice()->iterated();  // returned an iterable exactly 2 times

When used after generated() or iterated(), cardinality modifiers relate to the subset of calls that already satisfied the initial verification:

$subject->generated()->twice()->produced('a'); // produced 'a' from exactly 2 generator calls
$subject->iterated()->twice()->produced('a');  // produced 'a' from exactly 2 iterable calls

See Verifying cardinality with spies and Verifying cardinality with calls for a full list or cardinality modifiers.

Iterable spy substitution

Phony will sometimes accept an iterable spy, or generator spy, as equivalent to the iterable value it is spying on. This simplifies some stubbing and verification scenarios.

When stubbing, iterable spies and generator spies are substituted when matching stub arguments:

$stubA = stub()->setUseIterableSpies(true)->returnsArgument();
$iterable = ['a', 'b'];
$iterableSpy = $stubA($iterable);

$stubB = stub();

// these two statements are equivalent
$stubB->with($iterable)->returns('c');
$stubB->with($iterableSpy)->returns('c');

The same is true when verifying that a spy was called with specific arguments:

$stubA = stub()->setUseIterableSpies(true)->returnsArgument();
$iterable = ['a', 'b'];
$iterableSpy = $stubA($iterable);

$stubB = stub();
$stubB($iterableSpy);

// these two statements are equivalent
$database->select->calledWith($iterable);
$database->select->calledWith($iterableSpy);

And also when verifying spy return values:

$stub = stub()->setUseIterableSpies(true)->returnsArgument();
$iterable = ['a', 'b'];
$iterableSpy = $stub($iterable);

// these two statements are equivalent
$stub->returned($iterable);    // passes
$stub->returned($iterableSpy); // passes

There are other edge-case situations where Phony will exhibit this behavior. Refer to the API documentation for more detailed information.

Iterable verification caveats

Using iterable spies changes the return value

When generator spies are enabled, the return value of spied functions that produce generators will automatically be wrapped in another generator that spies on the original. This is usually not a problem, because it is impossible to distinguish the two generators aside from an identity comparison (===).

If the system under test relies upon the identity of returned generators, it may be advisable to disable generator spies using $spy->setUseGeneratorSpies(false).

Similarly, when iterable spies are enabled, the return value of functions that produce iterable values will automatically be wrapped in an iterator that spies on the original value. This can be more problematic than with generator spies, because it changes the type of the return value. That is why iterable spies are disabled by default.

Repeated iteration of iterable spies

Events are only recorded the first time an iterable spy is iterated over. If the underlying value is an array, or is iterated with an iterator that supports rewind(), subsequent iterations will behave as expected, but no events will be recorded.

This has no relevance for generator spies, as generators cannot be rewound.

Spying on iterables that implement array-like interfaces

It's quite common for iterable objects to also implement ArrayAccess and Countable. Iterable spies always implement these interfaces, which may be incompatible with code that checks for these interfaces at run time.

If the underlying iterable object implements ArrayAccess and/or Countable, Phony will pass relevant calls through to the underlying implementation. If these interfaces are not implemented, then the behavior of calls to ArrayAccess and/or Countable is undefined, but will most likely result in an error.

Nested iterable spies

It is possible for an iterable value to become nested in multiple iterable spies. Consider the following:

$spy = spy(
    function (Traversable $traversable) {
        return $traversable;
    }
);
$spy->setUseIterableSpies(true);

$traversable = new ArrayIterator(['a', 'b']);
$iterableSpy = $spy($traversable);
$nestedIterableSpy = $spy($iterableSpy);

// note that $traversable !== $iterableSpy !== $nestedIterableSpy
// in other words, they are all different iterator instances

iterator_to_array($nestedIterableSpy); // consume the outer-most traversable

$firstCall = $spy->callAt(0);          // the call that returned $iterableSpy
$firstCall->iterated()->produced('a'); // passes
$firstCall->iterated()->produced('b'); // passes

$secondCall = $spy->callAt(1);          // the call that returned $nestedIterableSpy
$secondCall->iterated()->produced('a'); // passes
$secondCall->iterated()->produced('b'); // passes

This example demonstrates that even when iterable spies are nested, in most cases this is desirable, because it means that the iterable events are recorded against both of the relevant calls.

Note that this nesting behavior can lead to some confusing results when an iterable is partially traversed before being wrapped in another iterable spy. Behavior in these circumstances is up to the PHP runtime, but generally speaking, it "just works".

Order verification

To verify that events happened in a particular order, use inOrder():

// with `use function`
inOrder(
    $spyA->calledWith('a'),
    $spyA->returned('x'),
    $spyB->calledWith('b'),
    $spyB->returned('y')
);

// without `use function`
Phony::inOrder(
    $spyA->calledWith('a'),
    $spyA->returned('x'),
    $spyB->calledWith('b'),
    $spyB->returned('y')
);

To relax order verification within a call to inOrder(), use anyOrder():

// with `use function`
inOrder(
    anyOrder(
        $spyA->calledWith('a'),
        $spyB->calledWith('b')
    ),
    anyOrder(
        $spyA->returned('x'),
        $spyB->returned('y')
    )
);

// without `use function`
Phony::inOrder(
    Phony::anyOrder(
        $spyA->calledWith('a'),
        $spyB->calledWith('b')
    ),
    Phony::anyOrder(
        $spyA->returned('x'),
        $spyB->returned('y')
    )
);

Event order can be verified across all defined mocks, stubs, and spies, using any verification implemented by Phony. Both inOrder() and anyOrder() will accept any number of verification results to verify.

Example output from inOrder():

Example output from inOrder()

Dynamic order verification

When the number of events to verify is dynamic, the [... operator] can be used:

$calledEvents = [];
$returnedEvents = [];

foreach ($spies as $spy) {
    $calledEvents[] = $spy->called();
    $returnedEvents[] = $spy->returned();
}

// with `use function`
inOrder(
    anyOrder(...$calledEvents),
    anyOrder(...$returnedEvents)
);

// without `use function`
Phony::inOrder(
    Phony::anyOrder(...$calledEvents),
    Phony::anyOrder(...$returnedEvents)
);

Order verification caveats

Intermediate events in order verification

Order verification does not verify that the specified events were the only events that occurred:

$spy('a');
$spy('b');
$spy('c');

$withA = $spy->calledWith('a');
$withB = $spy->calledWith('b');
$withC = $spy->calledWith('c');

inOrder($withA, $withB); // passes
inOrder($withA, $withC); // passes
inOrder($withB, $withC); // passes

To verify that specific events did not occur, use $spy->never() or $call->never().

Similar events in order verification

Order verification does not verify that the specified events happened in only one order:

$spy('a');
$spy('b');
$spy('a');

$withA = $spy->calledWith('a');
$withB = $spy->calledWith('b');

inOrder($withA, $withB);         // passes
inOrder($withB, $withA);         // passes
inOrder($withA, $withB, $withA); // passes

However, it is possible to verify event order more explicitly using individual calls:

inOrder($withA->firstCall(), $withB); // passes
inOrder($withA->lastCall(), $withB);  // fails

Of course, extracting individual calls is only helpful when verifying the order of calls. Similar methods exist to aid in explicitly verifying the order of other types of events:

$spy = spy(
    function ($x) {
        return $x;
    }
);

$spy('a');
$spy('b');
$spy('a');

$returnedA = $spy->returned('a');
$returnedB = $spy->returned('b');

inOrder($returnedA->firstEvent(), $returnedB); // passes
inOrder($returnedA->lastEvent(), $returnedB);  // fails

Verifying that there was no interaction with a mock

To verify that there was no interaction with a mock, use noInteraction() on any mock handle:

$handle = mock(ClassA::class);
$mock = $handle->get();

$handle->noInteraction(); // passes

$mock->methodA();

$handle->noInteraction(); // fails

This verification will fail if any of the mock's methods have been called.

Example output from noInteraction():

Example output from $handle->noInteraction()

Using colored verification output

To control the use of ANSI colored output, use setUseColor():

setUseColor($useColor);        // with `use function`
Phony::setUseColor($useColor); // without `use function`

Passing true turns color on, passing false turns color off, and passing null will cause Phony to automatically detect whether color should be used (this is the default behavior).

Matchers

Matchers are used to determine whether the arguments of a call match certain criteria. Utilized correctly, they can make verifications less brittle, and improve the quality of a test suite.

Phony implements only a few matchers itself, and provides first-class support for numerous third-party matcher libraries.

Matcher integrations

Kahlan argument matchers

Kahlan is a popular "describe-it" style testing framework. Kahlan argument matchers can be used in any Phony verification:

$spy->calledWith(Arg::toBe('a'));

Kahlan argument matchers are supported when using the eloquent/phony-kahlan package.

Hamcrest matchers

Hamcrest is a popular stand-alone matcher library, that originated in Java, and has been ported to many languages, including an "official" port for PHP. Its matchers can be used in any Phony verification:

$spy->calledWith(equalTo('a'));

Hamcrest matchers are supported regardless of which Composer package is in use.

PHPUnit constraints

PHPUnit is a popular unit testing framework. PHPUnit matchers (referred to as "constraints") can be used in any Phony verification:

// where $this is a PHPUnit test case
$spy->calledWith($this->equalTo('a'));

PHPUnit constraints are supported when using the eloquent/phony-phpunit package.

Shorthand matchers

Phony provides two "shorthand matchers"; '~', and '*'. These strings are automatically transformed into specific matchers:

For typical Phony usage, this results in much simpler verifications. For example, the following verifications are exactly equivalent:

$spy->calledWith('a', any(), 'b', wildcard());
$spy->calledWith('a', '~', 'b', '*');

In the case that a verification expects a literal '~', or '*' value, the "equal to" matcher can be used to prevent Phony interpreting shorthand matchers:

$spy->calledWith(equalTo('~'), equalTo('*'));

The "any" matcher

This matcher matches a single argument of any value:

$matcher = any();        // with `use function`
$matcher = Phony::any(); // without `use function`

$spy->calledWith(any()); // typical usage

The "equal to" matcher

This is the default matcher used by Phony. It takes a single argument, and matches values that are equal to that argument:

$matcher = equalTo($value);        // with `use function`
$matcher = Phony::equalTo($value); // without `use function`

$spy->calledWith(equalTo('a')); // typical usage

This matcher is equivalent to strict comparison (===), except that it does not require objects to be the same instance:

$matcher = equalTo((object) ['a' => 0]);

var_dump($matcher->matches((object) ['a' => 0]));    // outputs 'bool(true)'
var_dump($matcher->matches((object) ['a' => null])); // outputs 'bool(false)'

When to use the "equal to" matcher

In most cases, it's not necessary to create an "equal to" matcher manually, because Phony implicitly wraps anything that's not already a matcher. For example, this verification:

$spy->calledWith('a');

is exactly equivalent to this one:

$spy->calledWith(equalTo('a'));

In fact, there are only two circumstances in which this matcher should be used. The first is when a verification expects a literal * or ~ string (which Phony would otherwise interpret as special shorthand matchers):

$spy->calledWith(equalTo('~'), equalTo('*'));

The second circumstance is when a verification expects an actual matcher as an argument:

$spy->calledWith(equalTo(new EqualToMatcher('a')));

Special cases for the "equal to" matcher

For certain types of values, the "equal to" matcher will exhibit special behavior, in order to improve the usefulness of its comparisons, or to improve performance in common use cases.

Comparing exceptions

When an exception is compared, some internal PHP details are stripped from the output, including file path, line number, and stack trace.

In the following example, note that differing line numbers are ignored, but differing codes are not:

$matcher = equalTo(new Exception('x'));

$a = new Exception('x');
$b = new Exception('x', 1);

echo $matcher->matches($a) ? 'true' : 'false'; // outputs 'true'
echo $matcher->matches($b) ? 'true' : 'false'; // outputs 'false'
Comparing mocks

When a mock is compared, some internal Phony details are ignored. In addition, if a label has been set on the mock, it will be included in the comparison.

In the following example, note that differing mock behaviors are ignored, but differing labels are not:

$matcher = equalTo(mock(ClassX::class)->setLabel('x')->get());

$a = mock(ClassX::class)->setLabel('x');
$a->methodX->returns('x');

echo $matcher->matches($a->get()) ? 'true' : 'false'; // outputs 'true'

$b = mock(ClassX::class)->setLabel('y');
$c = mock(ClassX::class);

echo $matcher->matches($b->get()) ? 'true' : 'false'; // outputs 'false'
echo $matcher->matches($c->get()) ? 'true' : 'false'; // outputs 'false'

Since mocks are labeled with a unique integer by default, they can normally be used to differentiate calls without requiring the use of an 'identical to' matcher:

$a = mock(ClassX::class)->get();
$b = mock(ClassX::class)->get();

$stub = stub();
$stub->with($a)->returns('a');
$stub->with($b)->returns('b');

echo $stub($a); // outputs 'a'
echo $stub($b); // outputs 'b'

The "instance of" matcher

The "instance of" matcher is a matcher that is functionally equivalent to the instanceof operator:

$matcher = anInstanceOf($type);        // with `use function`
$matcher = Phony::anInstanceOf($type); // without `use function`

$spy->calledWith(anInstanceOf(Iterator::class)); // typical usage

Just like instanceof, the "instance of" matcher supports both class names and interfaces; which can be specified as either a string, or an object:

$arrayIterator = new ArrayIterator([]);
$emptyIterator = new EmptyIterator();
$nonIterator = (object) [];

$matcher = anInstanceOf(Iterator::class);

var_dump($matcher->matches($arrayIterator)); // outputs 'bool(true)'
var_dump($matcher->matches($emptyIterator)); // outputs 'bool(true)'
var_dump($matcher->matches($nonIterator));   // outputs 'bool(false)'

$matcher = anInstanceOf(new ArrayIterator([]));

var_dump($matcher->matches($arrayIterator)); // outputs 'bool(true)'
var_dump($matcher->matches($emptyIterator)); // outputs 'bool(false)'
var_dump($matcher->matches($nonIterator));   // outputs 'bool(false)'

The "wildcard" matcher

The "wildcard" matcher is a special matcher that can match multiple arguments:

$spy('a', 'b', 'c');

$spy->calledWith('a', wildcard()); // verification passes
$spy->calledWith('a', any());      // verification fails (not enough arguments)

It is usually much simpler to take advantage of shorthand matchers when dealing with wildcards:

$spy('a', 'b', 'c');

$spy->calledWith('a', '*'); // verification passes
$spy->calledWith('a', '~'); // verification fails (not enough arguments)

By default, wildcard matchers will match any argument value. This behavior can be modified by wrapping any non-wildcard matcher (including literal values) in a wildcard. This is accomplished by passing the matcher to the wildcard as the first argument:

$spy('a', 'a');

$spy->calledWith(wildcard(equalTo('a'))); // verification passes
$spy->calledWith(wildcard('a'));          // verification passes

$spy->calledWith(wildcard(equalTo('b'))); // verification fails
$spy->calledWith(wildcard('b'));          // verification fails

Wildcards can also have minimum and/or maximum options, which limit how many arguments they match. The second and third parameters, respectively, are for specifying the minimum, and maximum argument counts:

$spy('a', 'b', 'c');

$spy->calledWith(wildcard('~', 2, 3)); // verification passes
$spy->calledWith(wildcard('~', 4));    // verification fails (too few arguments)
$spy->calledWith(wildcard('~', 0, 2)); // verification fails (too many arguments)

The behavior of wildcard matchers is only well defined when they appear at the end of a matcher list. If a wildcard appears before any other matcher, any behavior exhibited by Phony is not guaranteed, and may change in future versions:

$spy->calledWith('*');           // this is supported
$spy->calledWith('a', 'b', '*'); // this is supported
$spy->calledWith('*', 'b', 'c'); // this is not supported
$spy->calledWith('a', '*', 'c'); // this is not supported

The exporter

When a Phony verification fails, the failure message will often contain string representations of the actual, or expected PHP values involved. These string representations are generated by Phony's internal exporter.

The export format

The exporter generates a concise, unambiguous, human-readable representation of any PHP value, including recursive objects and arrays:

Input value Exporter output
null 'null'
true 'true'
false 'false'
111 '111'
1.11 '1.110000e+0'
'1.11' '"1.11"'
"a\nb" '"a\nb"'
STDIN 'resource#1'
[1, 2] '#0[1, 2]'
['a' => 1, 'b' => 2] '#0["a": 1, "b": 2]
(object) ['a' => 1, 'b' => 2] '#0{a: 1, b: 2}'
new ClassA() 'ClassA#0{}'

Export identifiers and references

Exported arrays, objects, and "wrapper" types (such as mock handles) include a numeric identifier that can be used to identify repeated occurrences of the same value. This is represented as a number sign (#) followed by the identifier:

$value = (object) [];
// $value is exported as '#0{}'

When a value appears multiple times, its internal structure will only be described the first time. Subsequent appearances will be indicated by a reference to the value's identifier. This is represented as an ampersand (&) followed by the identifier:

$inner = [1, 2];
$value = [&$inner, &$inner];
// $value is exported as '#0[#1[1, 2], &1[]]'

$inner = (object) ['a' => 1];
$value = (object) ['b' => $inner, 'c' => $inner];
// $value is exported as '#0{b: #1{a: 1}, c: &1{}}'

$inner = mock(ClassA::class)->setLabel('mock-label');
$value = [$inner, $inner];
// $value is exported as '#0[handle#1(PhonyMock_ClassA_0#0{}[mock-label]), &1()]'
Export reference types

Array references appear followed by brackets (e.g. &0[]), object references appear followed by braces (e.g. &0{}), and wrapper references appear followed by parentheses (e.g. &0()):

$array = [];
$object = (object) [];
$weakRef = WeakReference::create($object);
$wrapper = spy('implode')->setLabel('spy-label');

$value = [&$array, &$array];
// $value is exported as '#0[#1[], &1[]]'

$value = [$object, $object];
// $value is exported as '#0[#0{}, &0{}]'

$value = [$weakRef, $weakRef];
// $value is exported as '#0[weak#1(#0{}), &1()]'

$value = [$wrapper, $wrapper];
// $value is exported as '#0[spy#1(implode)[spy-label], &1()]'

This is necessary in order to disambiguate references, because arrays and other types can sometimes have the same identifier:

$value = [
    (object) [],
    [
        (object) [],
    ],
];
// $value is exported as '#0[#0{}, #1[#1{}]]'
Export reference exclusions

As well as excluding the content, object references exclude the class name, and wrapper references exclude the type, for brevity:

$inner = new ClassA();
$inner->c = "d";
$value = (object) ['a' => $inner, 'b' => $inner];
// $value is exported as '#0{a: ClassA#1{c: "d"}, b: &1{}}'

$inner = mock();
$value = [$inner, $inner];
// $value is exported as '#0[handle#0(PhonyMock_0#1{}[0]), &0()]'
Export identifier persistence

Identifiers for objects and wrappers are persistent across invocations of the exporter, and share a single sequence of numbers:

$a = (object) [];
$b = (object) [];
$c = mock();

$valueA = [$a, $b, $c, $a];
// $valueA is exported as '#0[#0{}, #1{}, handle#2(PhonyMock_0#3{}[0]), &0{}]'

$valueB = [$b, $a, $b, $c];
// $valueB is exported as '#0[#1{}, #0{}, &1{}, handle#2(PhonyMock_0#3{}[0])]'

As of PHP 7.4, this is also true for arrays:

$a = [];
$b = [];

$valueA = [&$a, &$b, &$a];
// $valueA is exported as '#0[#1[], #2[], &1[]]' under PHP 7.4

$valueB = [&$b, &$a, &$b];
// $valueB is exported as '#3[#2[], #1[], &2[]]' under PHP 7.4

But due to limitations in earlier versions of PHP, array identifiers are only persistent within a single exporter invocation:

$a = [];
$b = [];

$valueA = [&$a, &$b, &$a];
$valueB = [&$b, &$a, &$b];
// both $valueA and $valueB are exported as '#0[#1[], #2[], &1[]]' under PHP 7.3

Exporting recursive values

If a recursive value is exported, the points of recursion are exported as references, in the same way that multiple instances of the same value are handled:

$value = [];
$value[] = &$value;
// $value is exported as '#0[&0[]]'

$value = (object) [];
$value->a = $value;
// $value is exported as '#0{a: &0{}}'

Exporter special cases

For certain types of values, the exporter will exhibit special behavior, in order to improve the usefulness of its output, or to improve performance in common use cases.

Exporting closures

When a closure is exported, the file path and start line number are included in the output:

$closure = function () {}; // file path is /path/to/example.php, line number is 123
// $closure is exported as 'Closure#0{}[example.php:123]'

Only the basename of the path is included, for brevity.

Exporting exceptions

When an exception is exported, some internal PHP details are stripped from the output, including file path, line number, and stack trace:

$exception = new Exception('a', 1, new Exception());
// $exception is exported as 'Exception#0{message: "a", code: 1, previous: Exception#1{}}'

Additionally, when the message is '', the code is 0, and/or the previous exception is null, these values are excluded for brevity:

$exception = new RuntimeException();
// $exception is exported as 'RuntimeException#0{}'
Exporting mocks

When a mock is exported, some internal Phony details are stripped from the output. In addition, if a label has been set on the mock, this will be included in the output:

$handle = mock(ClassA::class)->setLabel('mock-label');

$mock = $handle->get();
// $mock is exported as 'PhonyMock_ClassA_0#0{}[mock-label]'

When a mock handle is exported, it is represented as a wrapper around the mock itself:

$handle = mock(ClassA::class)->setLabel('mock-label');
// $handle is exported as 'handle#1(PhonyMock_ClassA_0#0{}[mock-label])'

When a static handle is exported, it is represented as a wrapper around the mock class:

$staticHandle = onStatic($handle);
// $staticHandle is exported as 'static-handle#0(PhonyMock_ClassA_0)'
Exporting stubs

When a stub is exported, it is represented as a wrapper around the callable entity that is stubbed, with the label also included in the output:

$stub = stub('implode')->setLabel('stub-label');
// $stub is exported as 'stub#0(implode)[stub-label]'

Anonymous stubs have a simpler representation, since they don't wrap any callable entity:

$stub = stub()->setLabel('stub-label');
// $stub is exported as 'stub#0[stub-label]'

In the case of method stubs, class name information is also included:

$handle = mock(ClassA::class)->setLabel('mock-label');
$staticHandle = onStatic($handle);

$stub = $handle->methodA->setLabel('stub-label');
// $stub is exported as 'stub#0(ClassA[mock-label]->methodA)[stub-label]'

$stub = $staticHandle->staticMethodA->setLabel('stub-label');
// $stub is exported as 'stub#1(ClassA::staticMethodA)[stub-label]'
Exporting spies

When a spy is exported, it is represented as a wrapper around the callable entity that is spied on, with the label also included in the output:

$spy = spy('implode')->setLabel('spy-label');
// $spy is exported as 'spy#0(implode)[spy-label]'

Anonymous spies have a simpler representation, since they don't wrap any callable entity:

$spy = spy()->setLabel('spy-label');
// $spy is exported as 'spy#0[spy-label]'

In the case of method spies, class name information is also included:

$object = new ClassA();

$spy = spy([$object, 'methodA'])->setLabel('spy-label');
// $spy is exported as 'spy#0(ClassA->methodA)[spy-label]'

$spy = spy([ClassA::class, 'staticMethodA'])->setLabel('spy-label');
// $spy is exported as 'spy#1(ClassA::staticMethodA)[spy-label]'

Export depth

For complicated nested structures, exporting the entire value right down to its innermost values is not always desirable. Phony sets a limit on how deep into a nested structure the exporter will descend.

When a value is beyond the export depth, and has sub-values, its contents will be replaced with a special notation that simply indicates how many sub-values exist within that value:

$value = [[], ['a', 'b', 'c']];
// $value is exported as '#0[#1[], #2[~3]]'

$value = [(object) [], (object) ['a', 'b', 'c']];
// $value is exported as '#0[#0{}, #1{~3}]'

Setting the export depth

To set the export depth, use setExportDepth():

setExportDepth($depth);        // with `use function`
Phony::setExportDepth($depth); // without `use function`

Where $depth is an integer indicating the desired export depth.

Negative values are treated as infinite depth, and will cause Phony to export values in their entirety. Note that this can produce immense amounts of output for large nested structures.

The API

The top-level API

This is the API presented by Phony once it is imported. These may be functions or static methods depending on the method of importing:


handle mock($types = []) (with use function)
handle Phony::mock($types = []) (without use function)

Create a new full mock, and return a mock handle.

Each value in $types can be either a class name, or an ad hoc mock definition. If only a single type is being mocked, the class name or definition can be passed without being wrapped in an array.

See Mocking basics.


handle partialMock($types = [], $arguments = []) (with use function)
handle Phony::partialMock($types = [], $arguments = []) (without use function)

Create a new partial mock, and return a mock handle.

Each value in $types can be either a class name, or an ad hoc mock definition. If only a single type is being mocked, the class name or definition can be passed without being wrapped in an array.

Omitting $arguments will cause the original constructor to be called with an empty argument list. However, if a null value is supplied for $arguments, the original constructor will not be called at all.

See Partial mocks, Calling a constructor manually.


builder mockBuilder($types = []) (with use function)
builder Phony::mockBuilder($types = []) (without use function)

Create a new mock builder.

Each value in $types can be either a class name, or an ad hoc mock definition. If only a single type is being mocked, the class name or definition can be passed without being wrapped in an array.


handle on($mock) (with use function)
handle Phony::on($mock) (without use function)

Returns a mock handle for $mock.


handle onStatic($class) (with use function)
handle Phony::onStatic($class) (without use function)

Returns a static static handle for $class.

See Static mocks.


stub stub($callback = null) (with use function)
stub Phony::stub($callback = null) (without use function)

Create a new stub.

See Stubbing an existing callable, Anonymous stubs.


spy spy($callback = null) (with use function)
spy Phony::spy($callback = null) (without use function)

Create a new spy.

See Spying on an existing callable, Anonymous spies.


stub stubGlobal($function, $namespace) (with use function)
stub Phony::stubGlobal($function, $namespace) (without use function)

Create a stub of a function in the global namespace, and declare it as a function in another namespace.

Stubs created via this function do not forward to the original function by default. This differs from stubs created by other methods.

See Stubbing global functions.


spy spyGlobal($function, $namespace) (with use function)
spy Phony::spyGlobal($function, $namespace) (without use function)

Create a spy of a function in the global namespace, and declare it as a function in another namespace.

See Spying on global functions.


void restoreGlobalFunctions() (with use function)
void Phony::restoreGlobalFunctions() (without use function)

Restores the behavior of any functions in the global namespace that have been altered via spyGlobal() or stubGlobal().

See Spying on global functions, Stubbing global functions.


matcher any() (with use function)
matcher Phony::any() (without use function)

Create a new "any" matcher.


matcher equalTo($value) (with use function)
matcher Phony::equalTo($value) (without use function)

Create a new "equal to" matcher.


matcher anInstanceOf($type) (with use function)
matcher Phony::anInstanceOf($type) (without use function)

Create a new "instance of" matcher.

The $type parameter accepts either a class name, an interface name, or an object.


wildcard wildcard($value = null, $minimumArguments = 0, $maximumArguments = -1) (with use function)
wildcard Phony::wildcard($value = null, $minimumArguments = 0, $maximumArguments = -1) (without use function)

Create a new "wildcard" matcher.

The $value parameter accepts a value, or a matcher, to check against each argument that the wildcard matches.

Negative values for $maximumArguments represent "no maximum".


mixed emptyValue($type) (with use function)
mixed Phony::emptyValue($type) (without use function)

Create a new "empty" value.

The $type parameter accepts a ReflectionType, which can be created via PHP's built-in reflection API.

This table details the "empty" value that will be returned for each type:

Type Empty value
no type, or nullable type null
bool false
int 0
float .0
string ''
array []
iterable []
object (object) []
stdClass (object) []
callable stub()
Closure function () {}
Generator (function () { yield from []; })()
ClassName mock(ClassName::class)->get()

verification inOrder(...$events) (with use function) throws AssertionException
verification Phony::inOrder(...$events) (without use function) throws AssertionException

Throws an exception unless the supplied events happened in chronological order.

Each value in $events should be an event, or a verification result.

See Order verification.


?verification checkInOrder(...$events) (with use function)
?verification Phony::checkInOrder(...$events) (without use function)

Checks if the supplied events happened in chronological order.

Each value in $events should be an event, or a verification result.

See Order verification, Check verification.


verification anyOrder(...$events) (with use function) throws AssertionException
verification Phony::anyOrder(...$events) (without use function) throws AssertionException

Throws an exception unless at least one event is supplied.

Each value in $events should be an event, or a verification result.

See Order verification.


?verification checkAnyOrder(...$events) (with use function)
?verification Phony::checkAnyOrder(...$events) (without use function)

Checks that at least one event is supplied.

Each value in $events should be an event, or a verification result.

See Order verification, Check verification.


void setUseColor($useColor) (with use function)
void Phony::setUseColor($useColor) (without use function)

Turn on or off the use of ANSI colored output.

Pass null to detect automatically.

See Using colored verification output.


int setExportDepth($depth) (with use function)
int Phony::setExportDepth($depth) (without use function)

Set the default export depth, and return the previous depth.

Negative depths are treated as infinite depth.

See Setting the export depth.

The mock handle API

Mock handles implement the following methods:

See also:


mock $handle->get()

Get the mock.

This method is not available on static mock handles.

See Mocking basics, Mock handles.


$handle->$method or stub $handle->stub($method, $isNewRule = true)

Get the stub for $method.

If false is passed for $isNewRule, a new rule will not be started.

See Mocking basics.


string $handle->label()

Get the label.

This method is not available on static mock handles.


fluent $handle->setLabel($label)

Set the label.

This method is not available on static mock handles.


fluent $handle->construct(...$arguments)

Call the original constructor.

This method does not support reference parameters.

This method is not available on static mock handles.

See Calling a constructor manually.


fluent $handle->constructWith($arguments = [])

Call the original constructor.

This method supports reference parameters.

This method is not available on static mock handles.

See Calling a constructor manually.


ReflectionClass $handle->class()

Get the generated mock class.


string $handle->className()

Get the class name of the generated mock class.


fluent $handle->full()

Turn the mock into a full mock.


fluent $handle->partial()

Turn the mock into a partial mock.


fluent $handle->proxy()

Use the supplied object as the implementation for all methods of the mock.

This method may help when partial mocking of a particular implementation is not possible; as in the case of a final class.

See Proxy mocks.


callable $handle->defaultAnswerCallback()

Get the default answer callback.


fluent $handle->setDefaultAnswerCallback($callback)

Set the default answer callback for all method stubs of this mock.

This method accepts a callback that takes the stub as the first argument.


verification $handle->noInteraction() throws AssertionException

Throws an exception unless there was no interaction with the mock.

See Verifying that there was no interaction with a mock.


?verification $handle->checkNoInteraction()

Checks if there was no interaction with the mock.

See Verifying that there was no interaction with a mock, Check verification.


fluent $handle->stopRecording()

Stop recording calls.

See Pausing mock recording.


fluent $handle->startRecording()

Start recording calls.

See Pausing mock recording.

The mock builder API

Mock builders implement the following methods:

See also:


builder clone $builder

Copy an existing mock builder.

See Copying mock builders.


array<string,ReflectionClass> $builder->types()

Get the types that will be mocked.

Returns a map of class name to class.


fluent $builder->like(...$types)

Add classes, interfaces, or traits.

Each type value can be either a class name, or an ad hoc mock definition.

See Customizing the mock class.


fluent $builder->addMethod($name, $callback = null)

Add a custom method.

See Customizing the mock class.


fluent $builder->addProperty($name, $value = null, $type = null)

Add a custom property.

If supplied, $type will be used literally as the type declaration for the property

See Customizing the mock class.


fluent $builder->addStaticMethod($name, $callback = null)

Add a custom static method.

See Customizing the mock class.


fluent $builder->addStaticProperty($name, $value = null, $type = null)

Add a custom static property.

If supplied, $type will be used literally as the type declaration for the property

See Customizing the mock class.


fluent $builder->addConstant($name, $value = null)

Add a custom class constant.

See Customizing the mock class.


fluent $builder->named($className)

Set the class name.

See Customizing the mock class.


bool $builder->isFinalized()

Returns true if this builder is finalized.

See Generating mock classes from a builder.


fluent $builder->finalize()

Finalize the mock builder.

See Generating mock classes from a builder.


bool $builder->isBuilt()

Returns true if the mock class has been built.

See Generating mock classes from a builder.


ReflectionClass $builder->build($createNew = false)

Generate and define the mock class.

Calling this method will finalize the mock builder.

See Generating mock classes from a builder.


string $builder->className()

Generate and define the mock class, and return the class name.

Calling this method will finalize the mock builder.

See Generating mock classes from a builder.


mock $builder->get()

Get a mock.

This method will return the last created mock, only creating a new mock if no existing mock is available.

If no existing mock is available, the created mock will be a full mock.

Calling this method will finalize the mock builder.

See Creating mocks from a builder.


mock $builder->full()

Create a new full mock.

This method will always create a new mock.

Calling this method will finalize the mock builder.

See Creating mocks from a builder.


mock $builder->partial(...$arguments)

Create a new partial mock.

The constructor will be called with $arguments.

This method will always create a new mock.

Calling this method will finalize the mock builder.

This method does not support reference parameters.

See Creating mocks from a builder.


mock $builder->partialWith($arguments = [])

Create a new partial mock.

The constructor will be called with $arguments, unless $arguments is null, in which case the constructor will not be called at all.

This method will always create a new mock.

Calling this method will finalize the mock builder.

This method supports reference parameters.

See Creating mocks from a builder.

The stub API

In addition to the spy API, stubs implement the following methods:

See also:


mixed $stub->self()

Get the self value of this stub.


fluent $stub->setSelf($self)

Set the self value of this stub.


callable $stub->defaultAnswerCallback()

Get the default answer callback.


fluent $stub->setDefaultAnswerCallback($callback)

Set the default answer callback of this stub.

This method accepts a callback that takes the stub as the first argument.


fluent $stub->with(...$arguments)

Modify the current criteria to match the supplied arguments.

This method supports mock handle substitution and iterable spy substitution.

See Matching stub arguments.


fluent $stub->calls(...$callbacks)

Add callbacks to be called as part of an answer.

Note that all supplied callbacks will be called in the same invocation.

See Invoking callables.


fluent $stub->callsWith($callback, $arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add a callback to be called as part of an answer.

Note that all supplied callbacks will be called in the same invocation.

This method supports mock handle substitution.

See Invoking callables.


fluent $stub->callsArgument(...$indices)

Add argument callbacks to be called as part of an answer.

Calling this method with no arguments is equivalent to calling it with a single argument of 0.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

Note that all supplied callbacks will be called in the same invocation.

See Invoking arguments.


fluent $stub->callsArgumentWith($index = 0, $arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add an argument callback to be called as part of an answer.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

This method supports reference parameters.

Note that all supplied callbacks will be called in the same invocation.

This method supports mock handle substitution.

See Invoking arguments.


fluent $stub->setsArgument($indexOrValue = null, $value = null)

Set the value of an argument passed by reference as part of an answer.

If called with no arguments, sets the first argument to null.

If called with one argument, sets the first argument to $indexOrValue.

If called with two arguments, sets the argument at $indexOrValue to $value.

This method supports mock handle substitution.

See Setting passed-by-reference arguments.


fluent $stub->does(...$callbacks)

Add callbacks as answers.

See Using a callable as an answer.


fluent $stub->doesWith($callback, $arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add a callback as an answer.

The supplied arguments support reference parameters.

This method supports mock handle substitution.

See Using a callable as an answer.


fluent $stub->forwards($arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add an answer that calls the wrapped callback.

The supplied arguments support reference parameters.

This method supports mock handle substitution.

See Forwarding to the original callable.


fluent $stub->returns(...$values)

Add answers that return values.

Calling this method with no arguments is equivalent to calling it with a single argument of null.

This method supports mock handle substitution.

See Returning values.


fluent $stub->returnsArgument($index = 0)

Add an answer that returns an argument.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Returning arguments.


fluent $stub->returnsSelf()

Add an answer that returns the self value.

See Returning the "self" value, Stub "self" values.


fluent $stub->throws(...$exceptions)

Add answers that throw exceptions.

Calling this method with no arguments is equivalent to calling it with a single argument of null.

This method supports mock handle substitution.

See Throwing exceptions.


generator-answer $stub->generates($values = [])

Add an answer that returns a generator, and return the answer for further behavior customization.

Any supplied $values will be yielded from the resulting generator.

See Stubbing generators, Yielding from a generator.

The generator answer API

When stubbing generators, calls to generates() produce "generator answers", which implement the following methods:

See also:


fluent $generatorAnswer->calls(...$callbacks)

Add callbacks to be called as part of the answer.

See Invoking callables in a generator.


fluent $generatorAnswer->callsWith($callback, $arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add callbacks to be called as part of the answer.

This method supports mock handle substitution.

See Invoking callables in a generator.


fluent $generatorAnswer->callsArgument(...$indices)

Add argument callbacks to be called as part of the answer.

Calling this method with no arguments is equivalent to calling it with a single argument of 0.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Invoking arguments in a generator.


fluent $generatorAnswer->callsArgumentWith($index = 0, $arguments = [], $prefixSelf = false, $suffixArgumentsObject = false, $suffixArguments = true)

Add an argument callback to be called as part of the answer.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

This method supports reference parameters.

This method supports mock handle substitution.

See Invoking arguments in a generator.


fluent $generatorAnswer->setsArgument($indexOrValue = null, $value = null)

Set the value of an argument passed by reference as part of the answer.

If called with no arguments, sets the first argument to null.

If called with one argument, sets the first argument to $indexOrValue.

If called with two arguments, sets the argument at $indexOrValue to $value.

This method supports mock handle substitution of the value.

See Setting passed-by-reference arguments in a generator.


fluent $generatorAnswer->yields($keyOrValue = null, $value = null)

Add a yielded value to the answer.

If both $keyOrValue and $value are supplied, the stub will yield like yield $keyOrValue => $value;.

If only $keyOrValue is supplied, the stub will yield like yield $keyOrValue;.

If no arguments are supplied, the stub will yield like yield;.

This method supports mock handle substitution.

See Yielding individual values from a generator.


fluent $generatorAnswer->yieldsFrom($values)

Add a set of yielded values to the answer.

The $values argument can be an array, an iterator, or even another generator.

This method supports mock handle substitution.

See Yielding multiple values from a generator.


stub $generatorAnswer->returns(...$values)

End the generator by returning a value.

Calling this method with no arguments is equivalent to calling it with a single argument of null.

This method supports mock handle substitution.

See Returning values from a generator.


stub $generatorAnswer->returnsArgument($index = 0)

End the generator by returning an argument.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Returning arguments from a generator.


stub $generatorAnswer->returnsSelf()

End the generator by returning the self value.

See Returning the "self" value from a generator, Stub "self" values.


stub $generatorAnswer->throws(...$exceptions)

End the generator by throwing an exception.

Calling this method with no arguments is equivalent to calling it with a single argument of null.

This method supports mock handle substitution.

See Throwing exceptions from a generator.

The spy API

Both spies and stubs implement the following methods:

See also:


$spy(...$arguments) or mixed $spy->invoke(...$arguments) throws Exception, Error

Invoke the spy, record the call, and return or throw the result.

This method does not support reference parameters.

See Invoking spies.


mixed $spy->invokeWith($arguments) throws Exception, Error

Invoke the spy, record the call, and return or throw the result.

This method supports reference parameters.

See Invoking spies.


string $spy->label()

Get the label.


fluent $spy->setLabel($label)

Set the label.


bool $spy->useGeneratorSpies()

Returns true if this spy uses generator spies.


fluent $spy->setUseGeneratorSpies($useGeneratorSpies)

Turn on or off the use of generator spies.


bool $spy->useIterableSpies()

Returns true if this spy uses iterable spies.


fluent $spy->setUseIterableSpies($useIterableSpies)

Turn on or off the use of iterable spies.


fluent $spy->stopRecording()

Stop recording calls.

See Pausing spy recording.


fluent $spy->startRecording()

Start recording calls.

See Pausing spy recording.


bool $spy->hasCalls()

Returns true if any calls were recorded.

See Call verification.


int $spy->callCount()

Get the number of calls.

See Call count.


array<call> $spy->allCalls()

Get all calls as an array.

See Call verification.


call $spy->firstCall() throws UndefinedCallException

Get the first call.

See Individual calls.


call $spy->lastCall() throws UndefinedCallException

Get the last call.

See Individual calls.


call $spy->callAt($index = 0) throws UndefinedCallException

Get the call at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Individual calls.


bool $spy->hasEvents()

Returns true if any events were recorded.

See Similar events in order verification.


int $spy->eventCount()

Get the number of events.

See Similar events in order verification.


array<event> $spy->allEvents()

Get all events as an array.

See Similar events in order verification.


event $spy->firstEvent() throws UndefinedEventException

Get the first event.

See Similar events in order verification.


event $spy->lastEvent() throws UndefinedEventException

Get the last event.

See Similar events in order verification.


event $spy->eventAt($index = 0) throws UndefinedEventException

Get the event at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Similar events in order verification.


verification $spy->called() throws AssertionException

Throws an exception unless called.

See Verifying that a call was made.


?verification $spy->checkCalled()

Checks if called.

See Verifying that a call was made, Check verification.


verification $spy->calledWith(...$arguments) throws AssertionException

Throws an exception unless called with the supplied arguments.

This method supports mock handle substitution and iterable spy substitution.

See Verifying that a spy was called with specific arguments.


?verification $spy->checkCalledWith(...$arguments)

Checks if called with the supplied arguments.

This method supports mock handle substitution and iterable spy substitution.

See Verifying that a spy was called with specific arguments, Check verification.


verification $spy->returned($value = null) throws AssertionException

Throws an exception unless this spy returned the supplied value.

When called with no arguments, this method simply checks that the spy returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying spy return values.


?verification $spy->checkReturned($value = null)

Checks if this spy returned the supplied value.

When called with no arguments, this method simply checks that the spy returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying spy return values, Check verification.


verification $spy->threw($type = null) throws AssertionException

Throws an exception unless this spy threw an exception of the supplied type.

When called with no arguments, this method simply checks that the spy threw any exception.

When called with a string, this method checks that the spy threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the spy threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the spy threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying spy exceptions.


?verification $spy->checkThrew($type = null)

Checks if this spy threw an exception of the supplied type.

When called with no arguments, this method simply checks that the spy threw any exception.

When called with a string, this method checks that the spy threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the spy threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the spy threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying spy exceptions, Check verification.


verification $spy->responded() throws AssertionException

Throws an exception unless this spy responded.

A spy that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress.


?verification $spy->checkResponded()

Checks if this spy responded.

A spy that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Check verification.


verification $spy->completed() throws AssertionException

Throws an exception unless this spy completed.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying spy progress.


?verification $spy->checkCompleted()

Checks if this spy completed.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying spy progress, Check verification.


generator-verification $spy->generated() throws AssertionException

Throws an exception unless this spy returned a generator.

See Verifying generators returned by spies, Generator and iterable verification.


?generator-verification $spy->checkGenerated()

Checks if this spy returned a generator.

See Verifying generators returned by spies, Generator and iterable verification.


iterable-verification $spy->iterated() throws AssertionException

Throws an exception unless this spy returned an iterable.

See Verifying iterables returned by spies, Generator and iterable verification.


?iterable-verification $spy->checkIterated()

Checks if this spy returned an iterable.

See Verifying iterables returned by spies, Generator and iterable verification.


fluent $spy->never()

Requires that the next verification never matches.

See Verifying that a call event happened an exact number of times.


fluent $spy->once()

Requires that the next verification matches only once.

See Verifying that a call event happened an exact number of times.


fluent $spy->twice()

Requires that the next verification matches exactly two times.

See Verifying that a call event happened an exact number of times.


fluent $spy->thrice()

Requires that the next verification matches exactly three times.

See Verifying that a call event happened an exact number of times.


fluent $spy->times($times)

Requires that the next verification matches exactly $times times.

See Verifying that a call event happened an exact number of times.


fluent $spy->atLeast($minimum)

Requires that the next verification matches a number of times greater than or equal to $minimum.

See Verifying that a spy event happened a bounded number of times.


fluent $spy->atMost($maximum)

Requires that the next verification matches a number of times less than or equal to $maximum.

See Verifying that a spy event happened a bounded number of times.


fluent $spy->between($minimum, $maximum)

Requires that the next verification matches a number of times greater than or equal to $minimum, and less than or equal to $maximum.

See Verifying that a spy event happened a bounded number of times.


fluent $spy->always()

Requires that the next verification matches for all possible items.

See Verifying that all spy events happen the same way.

The call API

Calls implement the following methods:

See also:


arguments $call->arguments()

Get the arguments.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


mixed $call->argument($index = 0) throws UndefinedArgumentException

Get an argument by index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


int $call->argumentCount()

Get the number of arguments.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


mixed $call->returnValue() throws UndefinedResponseException

Get the return value.

An UndefinedResponseException will be thrown if the call has not yet responded, or threw an exception.

See Verifying spy return values, Verifying call return values.


mixed $call->generatorReturnValue() throws UndefinedResponseException

Get the generator return value.

An UndefinedResponseException will be thrown if the call has not yet responded, did not respond by returning a generator, has not completed iteration, or if the generator ended by throwing an exception.

See Verifying generator return values.


Throwable $call->exception() throws UndefinedResponseException

Get the thrown exception.

An UndefinedResponseException will be thrown if the call has not yet responded, or did not throw an exception.

See Verifying spy exceptions, Verifying call exceptions.


Throwable $call->generatorException() throws UndefinedResponseException

Get the exception thrown by the generator.

An UndefinedResponseException will be thrown if the call has not yet responded, did not respond by returning a generator, has not completed iteration, or if the generator ended by returning a value.

See Verifying generator exceptions.


tuple<?Throwable,mixed> $call->response() throws UndefinedResponseException

Get the response.

This method returns a 2-tuple. The first element is the thrown exception, or null if no exception was thrown. The second element is the returned value, or null if an exception was thrown.

An UndefinedResponseException will be thrown if the call has not yet responded.

See Verifying spy progress, Verifying call progress.


tuple<?Throwable,mixed> $call->generatorResponse() throws UndefinedResponseException

Get the generator response.

This method returns a 2-tuple. The first element is the thrown exception, or null if no exception was thrown. The second element is the returned value, or null if an exception was thrown.

An UndefinedResponseException will be thrown if the call has not yet responded, did not respond by returning a generator, or if the generator has not completed iteration.

See Verifying spy progress, Verifying call progress.


bool $call->hasResponded()

Returns true if this call has responded.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Verifying call progress.


bool $call->isIterable()

Returns true if this call has responded with an iterable.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Verifying call progress.


bool $call->isGenerator()

Returns true if this call has responded with a generator.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Verifying call progress.


bool $call->hasCompleted()

Returns true if this call has completed.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying spy progress, Verifying call progress.


float $call->time()

Get the time at which the call occurred, in seconds since the Unix epoch.

See Verifying spy progress, Verifying call progress.


?float $call->responseTime()

Get the time at which the call responded, in seconds since the Unix epoch.

If the call has not yet responded, null will be returned.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Verifying call progress.


?float $call->endTime()

Get the time at which the call completed, in seconds since the Unix epoch.

If the call has not yet completed, null will be returned.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying spy progress, Verifying call progress.


?float $call->responseDuration()

Get the call response duration, in seconds.

If the call has not yet responded, null will be returned.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying spy progress, Verifying call progress.


?float $call->duration()

Get the call response duration, in seconds.

If the call has not yet completed, null will be returned.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying spy progress, Verifying call progress.


int $call->index()

Get the call index.

This number tracks the order of this call with respect to other calls made against the same spy.


int $call->sequenceNumber()

Get the sequence number.

The sequence number is a unique number assigned to every event that Phony records. The numbers are assigned sequentially, meaning that sequence numbers can be used to determine event order.


verification $call->calledWith(...$arguments) throws AssertionException

Throws an exception unless called with the supplied arguments.

This method supports mock handle substitution and iterable spy substitution.

See Verifying that a call was made with specific arguments.


?verification $call->checkCalledWith(...$arguments)

Checks if called with the supplied arguments.

This method supports mock handle substitution and iterable spy substitution.

See Verifying that a call was made with specific arguments, Check verification.


verification $call->returned($value = null) throws AssertionException

Throws an exception unless this call returned the supplied value.

When called with no arguments, this method simply checks that the call returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying call return values.


?verification $call->checkReturned($value = null)

Checks if this call returned the supplied value.

When called with no arguments, this method simply checks that the call returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying call return values, Check verification.


verification $call->threw($type = null) throws AssertionException

Throws an exception unless this call threw an exception of the supplied type.

When called with no arguments, this method simply checks that the call threw any exception.

When called with a string, this method checks that the call threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the call threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the call threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying call exceptions.


?verification $call->checkThrew($type = null)

Checks if this call threw an exception of the supplied type.

When called with no arguments, this method simply checks that the call threw any exception.

When called with a string, this method checks that the call threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the call threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the call threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying call exceptions, Check verification.


verification $call->responded() throws AssertionException

Throws an exception unless this call responded.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying call progress.


?verification $call->checkResponded()

Checks if this call responded.

A call that has "responded" has returned a value, or thrown an exception.

See Verifying call progress, Check verification.


verification $call->completed() throws AssertionException

Throws an exception unless this call completed.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying call progress.


?verification $call->checkCompleted()

Checks if this call completed.

When generator spies are in use, a call that returns a generator will not be considered "complete" until the generator has been completely consumed via iteration.

Similarly, when iterable spies are in use, a call that returns an iterable will not be considered "complete" until the iterable has been completely consumed via iteration.

See Verifying call progress, Check verification.


generator-verification $call->generated() throws AssertionException

Throws an exception unless this call returned a generator.

See Verifying generators returned by calls, Generator and iterable verification.


?generator-verification $call->checkGenerated()

Checks if this call returned a generator.

See Verifying generators returned by calls, Generator and iterable verification.


iterable-verification $call->iterated() throws AssertionException

Throws an exception unless this call returned an iterable.

See Verifying iterables returned by calls, Generator and iterable verification.


?iterable-verification $call->checkIterated()

Checks if this call returned an iterable.

See Verifying iterables returned by calls, Generator and iterable verification.


fluent $call->never()

Requires that the next verification never matches.

See Verifying that a call event happened an exact number of times.


fluent $call->once()

Requires that the next verification matches only once.

See Verifying that a call event happened an exact number of times.


fluent $call->twice()

Requires that the next verification matches exactly two times.

See Verifying that a call event happened an exact number of times.


fluent $call->thrice()

Requires that the next verification matches exactly three times.

See Verifying that a call event happened an exact number of times.


fluent $call->times($times)

Requires that the next verification matches exactly $times times.

See Verifying that a call event happened an exact number of times.


fluent $call->atLeast($minimum)

Requires that the next verification matches a number of times greater than or equal to $minimum.

See Verifying that a call event happened a bounded number of times.


fluent $call->atMost($maximum)

Requires that the next verification matches a number of times less than or equal to $maximum.

See Verifying that a call event happened a bounded number of times.


fluent $call->between($minimum, $maximum)

Requires that the next verification matches a number of times greater than or equal to $minimum, and less than or equal to $maximum.

See Verifying that a call event happened a bounded number of times.


fluent $call->always()

Requires that the next verification matches for all possible items.

See Verifying that all call events happen the same way.

The arguments API

When verifying that a spy was called with specific arguments, or verifying that a call was made with specific arguments, calls to arguments() produce objects which implement the following methods:

See also:


array<mixed> $arguments->all()

Get the arguments as an array.

Arguments passed by reference will be references in the returned array.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


count($arguments) or int $arguments->count()

Returns the total number of arguments.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


foreach ($arguments as $index => $argument) { /* ... */ }

Arguments implement the Traversable interface, allowing them to be used in a foreach statement.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


bool $arguments->has($index = 0)

Returns true if an argument exists at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


mixed $arguments->get($index = 0) throws UndefinedArgumentException

Get the argument at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


fluent $arguments->set($indexOrValue = null, $value = null) throws UndefinedArgumentException

Set an argument by index.

If called with no arguments, sets the first argument to null.

If called with one argument, sets the first argument to $indexOrValue.

If called with two arguments, sets the argument at $indexOrValue to $value.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.


arguments $arguments->copy()

Copy these arguments, breaking any references.

See Verifying that a spy was called with specific arguments, Verifying that a call was made with specific arguments.

The verification result API

All forms of verification produce "verification results" that implement the following methods:

See also:


bool $verification->hasCalls()

Returns true if this verification matched any calls.

See Call verification.


int $verification->callCount()

Get the number of calls.

See Call count.


array<call> $verification->allCalls()

Get all calls as an array.

See Call verification.


call $verification->firstCall() throws UndefinedCallException

Get the first call.

See Individual calls.


call $verification->lastCall() throws UndefinedCallException

Get the last call.

See Individual calls.


call $verification->callAt($index = 0) throws UndefinedCallException

Get the call at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Individual calls.


bool $verification->hasEvents()

Returns true if this verification matched any events.

See Similar events in order verification.


int $verification->eventCount()

Get the number of events.

See Similar events in order verification.


array<event> $verification->allEvents()

Get all events as an array.

See Similar events in order verification.


event $verification->firstEvent() throws UndefinedEventException

Get the first event.

See Similar events in order verification.


event $verification->lastEvent() throws UndefinedEventException

Get the last event.

See Similar events in order verification.


event $verification->eventAt($index = 0) throws UndefinedEventException

Get the event at $index.

Negative indices are offset from the end of the list. That is, -1 indicates the last element, and -2 indicates the second last element.

See Similar events in order verification.

The iterable verification result API

In addition to the verification result API, iterable verification results implement the following methods:

See also:


verification $verification->used() throws AssertionException

Throws an exception unless iteration of the iterable commenced.

See Verifying iteration.


?verification $verification->checkUsed()

Checks if iteration of the iterable commenced.

See Verifying iteration, Check verification.


verification $verification->produced($keyOrValue = null, $value = null) throws AssertionException

Throws an exception unless the iterable produced the supplied values.

When called with no arguments, this method simply checks that the iterable produced any value.

With a single argument, it checks that a value matching the argument was produced.

With two arguments, it checks that a key and value matching the respective arguments were produced together.

This method supports mock handle substitution and iterable spy substitution.

See Verifying produced values.


?verification $verification->checkProduced($keyOrValue = null, $value = null)

Checks if the iterable produced the supplied values.

When called with no arguments, this method simply checks that the iterable produced any value.

With a single argument, it checks that a value matching the argument was produced.

With two arguments, it checks that a key and value matching the respective arguments were produced together.

This method supports mock handle substitution and iterable spy substitution.

See Verifying produced values, Check verification.


verification $verification->consumed() throws AssertionException

Throws an exception unless iteration of the iterable completed.

See Verifying iteration.


?verification $verification->checkConsumed()

Checks if iteration of the iterable completed.

See Verifying iteration, Check verification.


fluent $verification->never()

Requires that the next verification never matches.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened an exact number of times, Verifying that a call event happened an exact number of times.


fluent $verification->once()

Requires that the next verification matches only once.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened an exact number of times, Verifying that a call event happened an exact number of times.


fluent $verification->twice()

Requires that the next verification matches exactly two times.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened an exact number of times, Verifying that a call event happened an exact number of times.


fluent $verification->thrice()

Requires that the next verification matches exactly three times.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened an exact number of times, Verifying that a call event happened an exact number of times.


fluent $verification->times($times)

Requires that the next verification matches exactly $times times.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened an exact number of times, Verifying that a call event happened an exact number of times.


fluent $verification->atLeast($minimum)

Requires that the next verification matches a number of times greater than or equal to $minimum.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened a bounded number of times, Verifying that a call event happened a bounded number of times.


fluent $verification->atMost($maximum)

Requires that the next verification matches a number of times less than or equal to $maximum.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened a bounded number of times, Verifying that a call event happened a bounded number of times.


fluent $verification->between($minimum, $maximum)

Requires that the next verification matches a number of times greater than or equal to $minimum, and less than or equal to $maximum.

See Verifying cardinality with generators and iterables, Verifying that a spy event happened a bounded number of times, Verifying that a call event happened a bounded number of times.


fluent $verification->always()

Requires that the next verification matches for all possible items.

See Verifying cardinality with generators and iterables, Verifying that all spy events happen the same way, Verifying that all call events happen the same way.

The generator verification result API

In addition to the verification result API and the iterable verification result API, generator verification results implement the following methods:

See also:


verification $verification->received($value = null) throws AssertionException

Throws an exception unless the generator received the supplied value.

When called with no arguments, this method simply checks that the generator received any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying values received by generators.


?verification $verification->checkReceived($value = null)

Checks if the generator received the supplied value.

When called with no arguments, this method simply checks that the generator received any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying values received by generators, Check verification.


verification $verification->receivedException($type = null) throws AssertionException

Throws an exception unless the generator received an exception of the supplied type.

When called with no arguments, this method simply checks that the generator received any exception.

When called with a string, this method checks that the generator received an exception that is an instance of $type.

When called with an exception instance, this method checks that the generator received an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the generator received an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying exceptions received by generators.


?verification $verification->checkReceivedException($type = null)

Checks if the generator received an exception of the supplied type.

When called with no arguments, this method simply checks that the generator received any exception.

When called with a string, this method checks that the generator received an exception that is an instance of $type.

When called with an exception instance, this method checks that the generator received an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the generator received an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying exceptions received by generators, Check verification.


verification $verification->returned($value = null) throws AssertionException

Throws an exception unless the generator returned the supplied value.

When called with no arguments, this method simply checks that the generator returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying generator return values.


?verification $verification->checkReturned($value = null)

Checks if the generator returned the supplied value.

When called with no arguments, this method simply checks that the generator returned any value.

This method supports mock handle substitution and iterable spy substitution.

See Verifying generator return values, Check verification.


verification $verification->threw($type = null) throws AssertionException

Throws an exception unless this generator threw an exception of the supplied type.

When called with no arguments, this method simply checks that the generator threw any exception.

When called with a string, this method checks that the generator threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the generator threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the generator threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying generator exceptions.


?verification $verification->checkThrew($type = null)

Checks if this generator threw an exception of the supplied type.

When called with no arguments, this method simply checks that the generator threw any exception.

When called with a string, this method checks that the generator threw an exception that is an instance of $type.

When called with an exception instance, this method checks that the generator threw an exception that is equal to the supplied instance.

When called with a matcher, this method checks that the generator threw an exception that matches the supplied matcher.

This method supports mock handle substitution and iterable spy substitution.

See Verifying generator exceptions, Check verification.

The event API

When events are exposed from an API, they implement the following methods:

See also:


float $event->time()

Get the time at which the event occurred, in seconds since the Unix epoch.


int $event->sequenceNumber()

Get the sequence number.

The sequence number is a unique number assigned to every event that Phony records. The numbers are assigned sequentially, meaning that sequence numbers can be used to determine event order.

The matcher API

Matchers implement the following methods:

See also:


bool $matcher->matches($value)

Returns true if $value matches this matcher's criteria.

See Matchers.


"$matcher" or string $matcher->describe()

Describe this matcher.

See Matchers.

The wildcard matcher API

The "wildcard" matcher implements the following methods:

See also:


matcher $wildcard->matcher()

Get the matcher to use for each argument.

See The "wildcard" matcher.


int $wildcard->minimumArguments()

Get the minimum number of arguments to match.

See The "wildcard" matcher.


int $wildcard->maximumArguments()

Get the maximum number of arguments to match.

Negative values are used to represent "no maximum".

See The "wildcard" matcher.


"$wildcard" or string $wildcard->describe()

Describe this matcher.

See The "wildcard" matcher.

Thrown exceptions

AssertionException

Thrown when a verification fails. The exact exception class and implementation depends on the testing framework in use. See Standard verification.

Other than the standard PHP Exception methods, assertion exceptions have no public API methods.

UndefinedArgumentException

Thrown when an argument that was requested does not exist.

Namespace: Eloquent\Phony\Call\Exception


int $exception->index()

Get the index.

UndefinedCallException

Thrown when a call that was requested does not exist.

Namespace: Eloquent\Phony\Call\Exception


int $exception->index()

Get the index.

UndefinedEventException

Thrown when an event that was requested does not exist.

Namespace: Eloquent\Phony\Event\Exception


int $exception->index()

Get the index.

UndefinedResponseException

Thrown when the call has not yet produced a response of the requested type.

This can occur when an individual call is queried for its response details before the call has returned a value, or thrown an exception.

Other than the standard PHP Exception methods, undefined response exceptions have no public API methods.

License

For the full copyright and license information, please view the LICENSE file.