Phony
- Installation
- Guides
- Help
- Usage
- Mocks
-
Stubs
- Stubbing an existing callable
- Anonymous stubs
- Stub "self" values
- Stub rules and answers
- Matching stub arguments
- Returning values
- Returning arguments
- Returning the "self" value
- Throwing exceptions
- Using a callable as an answer
- Forwarding to the original callable
- Answers that perform multiple actions
- Stubbing generators
- Spies
- Calls
-
Verification
- Standard verification
- Check verification
- Understanding verification output
-
Generator and iterable verification
- Verifying iteration
- Verifying produced values
- Verifying values received by generators
- Verifying exceptions received by generators
- Verifying generator return values
- Verifying generator exceptions
- Verifying cardinality with generators and iterables
- Iterable spy substitution
- Iterable verification caveats
- Order verification
- Verifying that there was no interaction with a mock
- Using colored verification output
- Matchers
- The exporter
-
The API
- The top-level API
- The mock handle API
- The mock builder API
- The stub API
- The generator answer API
- The spy API
- The call API
- The arguments API
- The verification result API
- The iterable verification result API
- The generator verification result API
- The event API
- The matcher API
- The wildcard matcher API
- Thrown exceptions
- License
Installation
Available as various Composer packages, depending on the test framework in use:
- For Kahlan, use eloquent/phony-kahlan and import
Eloquent\Phony\Kahlan
. - For PHPUnit, use eloquent/phony-phpunit and import
Eloquent\Phony\Phpunit
. - For Peridot, use eloquent/phony-peridot and import
Eloquent\Phony
. - For other frameworks, or standalone usage, use eloquent/phony and import
Eloquent\Phony
.
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
- The documentation can be searched with the standard search shortcut:
- ⌘ + F for macOS and OS X
- Ctrl + F for other platforms
- The menu automatically opens to the correct section as the documentation is scrolled.
- An expanded table of contents can be viewed by clicking the "+" symbol in the menu.
- Documentation for other versions can be selected in the bottom-right corner.
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:
- For Kahlan, use eloquent/phony-kahlan and import
Eloquent\Phony\Kahlan
. - For PHPUnit, use eloquent/phony-phpunit and import
Eloquent\Phony\Phpunit
. - For Peridot, use eloquent/phony-peridot and import
Eloquent\Phony
. - For other frameworks, or standalone usage, use eloquent/phony and import
Eloquent\Phony
.
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.
use function
Importing withIf 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);
use function
Importing withoutA 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:
- Override the default treatment of values
- Define static methods or properties
- Define class constants
- Define typed properties
$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:
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:
- The function must be called without any qualifying namespace or backslash.
- The function must be called from within a namespace; function calls from the global namespace cannot be stubbed.
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:
- The function must be called without any qualifying namespace or backslash.
- The function must be called from within a namespace; function calls from the global namespace cannot be spied on.
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()
:
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()
:
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()
:
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()
:
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()
:
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()
:
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 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:
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()
:
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()
:
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()
:
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()
:
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()
:
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 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:
- Spies
- Calls
-
Verification
- Standard verification
- Check verification
- Understanding verification output
-
Generator and iterable verification
- Verifying iteration
- Verifying produced values
- Verifying values received by generators
- Verifying exceptions received by generators
- Verifying generator return values
- Verifying generator exceptions
- Verifying cardinality with generators and iterables
- Iterable spy substitution
- Iterable verification caveats
- Order verification
- Verifying that there was no interaction with a mock
- Using colored verification output
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:
Expected behavior output
The first part of the output explains what was expected. In the above example:
-
Expected ClassA[label]->methodA call
- Expected a call tomethodA
on a mock ofClassA
, with a label oflabel
. -
with arguments:
- The arguments should match the conditions detailed below. -
✓ "aardvark" (1 match)
- The first argument should be the string"aardvark"
, and 1 call had a matching first argument. -
✗ #0["bonobo", "chameleon", "dugong"] (0 matches)
- The second argument should be an array with 3 string elements, but no calls had a matching second argument.
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:
-
Matched 0 of 2:
- There were 2 calls, and neither of them matched.
Actual behavior output
The third part of the output explains what actually happened. In the above example:
-
✗ Call #0:
- The first call did not match. -
✓ "aardvark"
- The first argument of the first call matched. -
✗ #0["bonobo", "[-chameleon-]{+capybara+}", "dugong"]
- The second argument of the first call did not match. The second array element was expected to be"chameleon"
, but it was actually"capybara"
. -
✗ Call #1:
- The second call did not match. -
✗ "[-aardvark-]{+armadillo+}"
- The first argument of the second call did not match. The argument was expected to be"aardvark"
, but it was actually"armadillo"
. -
✗ #0["bonobo", "chameleon", "[-dugong-]{+dormouse+}"]
- The second argument of the second call did not match. The third array element was expected to be"dugong"
, but it was actually"dormouse"
.
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 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()
:
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()
:
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()
:
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()
:
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()
:
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()
:
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()
:
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:
-
'~'
is equivalent to an "any" matcher -
'*'
is equivalent to a "wildcard" matcher
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:
mock()
partialMock()
mockBuilder()
on()
onStatic()
stub()
spy()
stubGlobal()
spyGlobal()
restoreGlobalFunctions()
any()
equalTo()
anInstanceOf()
wildcard()
emptyValue()
inOrder()
checkInOrder()
anyOrder()
checkAnyOrder()
setUseColor()
setExportDepth()
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.
The mock handle API
Mock handles implement the following methods:
$handle->get()
$handle->$method
$handle->stub()
$handle->label()
$handle->setLabel()
$handle->construct()
$handle->constructWith()
$handle->class()
$handle->className()
$handle->full()
$handle->partial()
$handle->proxy()
$handle->defaultAnswerCallback()
$handle->setDefaultAnswerCallback()
$handle->noInteraction()
$handle->checkNoInteraction()
$handle->stopRecording()
$handle->startRecording()
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.
fluent $handle->startRecording()
Start recording calls.
The mock builder API
Mock builders implement the following methods:
clone $builder
$builder->types()
$builder->like()
$builder->addMethod()
$builder->addProperty()
$builder->addStaticMethod()
$builder->addStaticProperty()
$builder->addConstant()
$builder->named()
$builder->isFinalized()
$builder->finalize()
$builder->isBuilt()
$builder->build()
$builder->className()
$builder->get()
$builder->full()
$builder->partial()
$builder->partialWith()
See also:
Copy an existing mock builder.
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:
$stub->self()
$stub->setSelf()
$stub->defaultAnswerCallback()
$stub->setDefaultAnswerCallback()
$stub->with()
$stub->calls()
$stub->callsWith()
$stub->callsArgument()
$stub->callsArgumentWith()
$stub->setsArgument()
$stub->does()
$stub->doesWith()
$stub->forwards()
$stub->returns()
$stub->returnsArgument()
$stub->returnsSelf()
$stub->throws()
$stub->generates()
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.
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:
$generatorAnswer->calls()
$generatorAnswer->callsWith()
$generatorAnswer->callsArgument()
$generatorAnswer->callsArgumentWith()
$generatorAnswer->setsArgument()
$generatorAnswer->yields()
$generatorAnswer->yieldsFrom()
$generatorAnswer->returns()
$generatorAnswer->returnsArgument()
$generatorAnswer->returnsSelf()
$generatorAnswer->throws()
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.
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.
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:
$spy()
$spy->invoke()
$spy->invokeWith()
$spy->label()
$spy->setLabel()
$spy->useGeneratorSpies()
$spy->setUseGeneratorSpies()
$spy->useIterableSpies()
$spy->setUseIterableSpies()
$spy->stopRecording()
$spy->startRecording()
$spy->hasCalls()
$spy->callCount()
$spy->allCalls()
$spy->firstCall()
$spy->lastCall()
$spy->callAt()
$spy->hasEvents()
$spy->eventCount()
$spy->allEvents()
$spy->firstEvent()
$spy->lastEvent()
$spy->eventAt()
$spy->called()
$spy->checkCalled()
$spy->calledWith()
$spy->checkCalledWith()
$spy->returned()
$spy->checkReturned()
$spy->threw()
$spy->checkThrew()
$spy->responded()
$spy->checkResponded()
$spy->completed()
$spy->checkCompleted()
$spy->generated()
$spy->checkGenerated()
$spy->iterated()
$spy->checkIterated()
$spy->never()
$spy->once()
$spy->twice()
$spy->thrice()
$spy->times()
$spy->atLeast()
$spy->atMost()
$spy->between()
$spy->always()
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.
fluent $spy->startRecording()
Start recording calls.
bool $spy->hasCalls()
Returns true
if any calls were recorded.
See Call verification.
int $spy->callCount()
Get the number of calls.
See Call count.
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.
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.
?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.
?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.
?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:
$call->arguments()
$call->argument()
$call->argumentCount()
$call->returnValue()
$call->generatorReturnValue()
$call->exception()
$call->generatorException()
$call->response()
$call->generatorResponse()
$call->hasResponded()
$call->isIterable()
$call->isGenerator()
$call->hasCompleted()
$call->time()
$call->responseTime()
$call->endTime()
$call->responseDuration()
$call->duration()
$call->index()
$call->sequenceNumber()
$call->calledWith()
$call->checkCalledWith()
$call->returned()
$call->checkReturned()
$call->threw()
$call->checkThrew()
$call->responded()
$call->checkResponded()
$call->completed()
$call->checkCompleted()
$call->generated()
$call->checkGenerated()
$call->iterated()
$call->checkIterated()
$call->never()
$call->once()
$call->twice()
$call->thrice()
$call->times()
$call->atLeast()
$call->atMost()
$call->between()
$call->always()
See also:
$spy->allCalls()
$spy->firstCall()
$spy->lastCall()
$spy->callAt()
$verification->allCalls()
$verification->firstCall()
$verification->lastCall()
$verification->callAt()
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.
?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.
?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.
$verification->received()
$verification->checkReceived()
$verification->receivedException()
$verification->checkReceivedException()
$verification->returned()
$verification->checkReturned()
$verification->threw()
$verification->checkThrew()
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:
$arguments->all()
count($arguments)
$arguments->count()
foreach ($arguments as $index => $argument)
$arguments->has()
$arguments->get()
$arguments->set()
$arguments->copy()
See also:
$call->arguments()
$stub->callsWith()
$stub->callsArgumentWith()
$stub->doesWith()
$stub->forwards()
$generatorAnswer->callsWith()
$generatorAnswer->callsArgumentWith()
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.
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:
$verification->hasCalls()
$verification->callCount()
$verification->allCalls()
$verification->firstCall()
$verification->lastCall()
$verification->callAt()
$verification->hasEvents()
$verification->eventCount()
$verification->allEvents()
$verification->firstEvent()
$verification->lastEvent()
$verification->eventAt()
See also:
$spy->called()
$spy->checkCalled()
$spy->calledWith()
$spy->checkCalledWith()
$spy->returned()
$spy->checkReturned()
$spy->threw()
$spy->checkThrew()
$spy->responded()
$spy->checkResponded()
$spy->completed()
$spy->checkCompleted()
$spy->generated()
$spy->checkGenerated()
$spy->iterated()
$spy->checkIterated()
$call->calledWith()
$call->checkCalledWith()
$call->returned()
$call->checkReturned()
$call->threw()
$call->checkThrew()
$call->responded()
$call->checkResponded()
$call->completed()
$call->checkCompleted()
$call->generated()
$call->checkGenerated()
$call->iterated()
$call->checkIterated()
$verification->used()
$verification->checkUsed()
$verification->produced()
$verification->checkProduced()
$verification->consumed()
$verification->checkConsumed()
$verification->received()
$verification->checkReceived()
$verification->receivedException()
$verification->checkReceivedException()
$verification->returned()
$verification->checkReturned()
$verification->threw()
$verification->checkThrew()
$handle->noInteraction()
$handle->checkNoInteraction()
inOrder()
checkInOrder()
anyOrder()
checkAnyOrder()
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.
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.
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:
$verification->used()
$verification->checkUsed()
$verification->produced()
$verification->checkProduced()
$verification->consumed()
$verification->checkConsumed()
$verification->never()
$verification->once()
$verification->twice()
$verification->thrice()
$verification->times()
$verification->atLeast()
$verification->atMost()
$verification->between()
$verification->always()
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:
$verification->received()
$verification->checkReceived()
$verification->receivedException()
$verification->checkReceivedException()
$verification->returned()
$verification->checkReturned()
$verification->threw()
$verification->checkThrew()
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:
$spy->allEvents()
$spy->firstEvent()
$spy->lastEvent()
$spy->eventAt()
$verification->allEvents()
$verification->firstEvent()
$verification->lastEvent()
$verification->eventAt()
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:
$wildcard->matcher()
$wildcard->minimumArguments()
$wildcard->maximumArguments()
"$wildcard"
$wildcard->describe()
See also:
Get the matcher to use for each argument.
int $wildcard->minimumArguments()
Get the minimum number of arguments to match.
int $wildcard->maximumArguments()
Get the maximum number of arguments to match.
Negative values are used to represent "no maximum".
"$wildcard" or string $wildcard->describe()
Describe this 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.