teamgantt / phony
Mocks, stubs, and spies for PHP.
Requires
- php: ^8.3
Requires (Dev)
- ext-pdo: *
- eloquent/code-style: ^2
- eloquent/phpstan-phony: ^0.8
- friendsofphp/php-cs-fixer: ^3
- hamcrest/hamcrest-php: ^2
- phpstan/extension-installer: ^1
- phpstan/phpstan: ^1
- phpstan/phpstan-phpunit: ^1
- phpunit/phpunit: ^9
This package is auto-updated.
Last update: 2024-11-05 21:25:15 UTC
README
Mocks, stubs, and spies for PHP.
Installation
Available as various Composer packages, depending on the test framework in use:
- For Kahlan, use teamgantt/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 teamgantt/phony and import
Eloquent\Phony
.
See the section on Integration with test frameworks in the documentation.
Documentation
Full documentation is available.
What is Phony?
Phony is a PHP library for creating various kinds of test doubles, including object mocks, function stubs, and function spies.
Why use Phony?
Support for language features, old and new
Phony is committed to supporting new PHP features as they emerge. Features that require workarounds in other mocking frameworks (such as passed-by-reference arguments), typically "just work" with Phony.
Amongst other features, Phony supports:
- Function-level stubs and spies, which negate the need for object mocking in many cases
- Generator stubbing and verification
- Mocking of traits
- Stubbing of functions and methods with return types
- Modern variable-length argument lists using the
...
token - Setting of passed-by-reference arguments, and reference arguments in general
Zero-configuration integrations
Integrating Phony with your favorite test framework is as simple as choosing the correct namespace to import. Complimentary features make integrations seamless and intuitive:
- No configuration or bootstrap code necessary
- Most test frameworks need no special treatment
- Tight integration with Kahlan and PHPUnit
- Can be used standalone, too
- Supports matchers from Hamcrest, Kahlan, and PHPUnit
Interested in better integration with other test frameworks? So are we! Just open a GitHub issue if there's something we can do.
Refined verification output
Phony has received a great deal of work and refinement where the rubber meets the road; in its verification output:
Phony's output includes succinct but detailed information that is focused on helping you find the cause of the failure as fast as possible. Where appropriate, the output uses difference comparison and color to further highlight important details.
First-class support for functions and callbacks
Phony is designed to provide support for testing both object-based, and function-based systems. In Phony, object mocks are built upon full-featured function level stubs and spies.
This focus on support for procedural programming allows Phony to handle many situations that cannot be handled by solely class-based mocking frameworks. Since Phony's class-level support is based upon its function-level support, the interfaces are consistent, and require very little extra knowledge to use.
Extensive feature set
Phony has an extensive and powerful feature set. For detailed information on a particular feature, select one of the following:
- Mock objects
- Mocking of classes, interfaces, and traits
- Mocking of multiple types simultaneously
- Partial mocks
- Proxy mocks for
final
classes - Mock classes with custom methods and properties
- Constructor bypassing, and manual constructor calling
- Static method mocking
- Mocking of fluent interfaces
- Customizable class names
- Mock builders for advanced usage
- Stubbing
- Spying
- Verify-after-running style produces more durable tests than outdated expect-run-verify style
- Verification of calls and call arguments
- Verification of return values and thrown exceptions
- Verification of generators
- Verification of other iterables
- Order verification
- Individual call level verification
- Retrieval of call arguments
- And more...
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
For detailed usage, see the documentation.
Example test suites
See the phony-examples repository.
Standalone usage
Install the teamgantt/phony package, then:
use function Eloquent\Phony\mock; $handle = mock('ClassA'); $handle->methodA->with('argument')->returns('value'); $mock = $handle->get(); assert($mock->methodA('argument') === 'value'); $handle->methodA->calledWith('argument');
Kahlan usage
Install the teamgantt/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 teamgantt/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'); $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'); $handle->methodA->with('argument')->returns('value'); $mock = $handle->get(); $this->assertSame('value', $mock->methodA('argument')); $handle->methodA->calledWith('argument'); } }
Inception
Please forgive me if this section is opinionated, or if I recall some particular detail of a framework incorrectly. But if you want a TL;DR for why I created Phony, basically:
- Phake doesn't support traits, and once upon a time, didn't support HHVM.
- PHPUnit and Mockery both implement expect-run-verify style mocks, which are evil.
- Prophecy doesn't support the features that I need.
- SimpleTest is ancient.
- I hadn't heard of anything else that sounded promising.
The first mocking framework I used was probably SimpleTest's. Unfortunately, that's a long time ago now, and I didn't really understand mocking at the time. And to top it off, I can't even remember how SimpleTest's mocks functioned. So let's skip ahead to the first mocking framework I really explored in depth, which was PHPUnit's.
When I first discovered PHPUnit's mocks, they were revolutionary to me. They allowed me to test things really thoroughly, in ways I didn't even know were possible previously. Although PHPUnit's mocking was likely a port of some existing Java mocking system, it was the framework that first opened my eyes to the real potential of test doubles.
Unfortunately it wasn't all sunshine and roses. PHPUnit's mocks were difficult to use, and especially, because of the expect-run-verify style interface, they were difficult to re-use. There was no way to "un-expect" something in a particular test, and when something failed, the true cause of the failure was often difficult to determine.
While searching for a better solution, I stumbled across Phake, which was inspired by Java's extremely popular Mockito mocking framework. Phake was, and still is, an excellent mocking framework. Both Mockito and Phake eschew the expect-run-verify pattern, to great benefit.
By treating stubbing and verification as separate concepts, Phake essentially fixed all of the problems I had with PHPUnit's mocks. Mocks could be re-used easily, and when a test failed, the cause was (usually) clear. I was sold on the evils of expect-run-verify, and swore never to go back.
I believe it was around this time that I heard of Mockery. Although I was fairly happy with Phake, it did have a few little oddities, such as the way it deals with by-reference arguments, and mocking of traits was not possible. So I checked out Mockery, but was immediately put off by its use of expectations; which I felt would have been a huge step backwards.
In fairness, it's possible that Mockery supports other mocking methods, but since the "primary" way it works seems to be based around expect-run-verify, I've never considered it a viable candidate, and have never used it.
At some point around this time I worked on a Node.js project, and explored a handful of the most popular Node mocking frameworks, before settling on the excellent Sinon.JS. Its focus on callback-based stubbing and spying, and its extensive verification API would eventually influence Phony heavily.
It wasn't until HHVM became a viable option in the PHP world that I really had to consider using something other than Phake. I had wanted for a while to start experimenting with HHVM in my projects. Unfortunately Phake had issues that prevented the test suite from even running under HHVM, so it was immediately a problem.
One of my co-workers had intended to work on HHVM support for Phake, but unfortunately it seemed at that time as though work on Phake had stagnated, and it took over a month just to get a PR accepted that added HHVM as a test target. To Phake's credit, HHVM is now supported, and hindsight has taught me that HHVM support is hard.
One project that showed promise was Prophecy, an "opinionated" mocking framework that arose from the phpspec testing framework. While I disliked its use of abstract terms like "prophesize" and "reveal" for method names, the core idea of a separate object instance that can be used to control the actual mock worked really well. So well, in fact, that I would eventually end up using this concept in Phony.
Importantly, Prophecy already supported HHVM, and seemed like a great fit to replace Phake; until I ran into the "opinionated" side of Prophecy's nature. One thing that Prophecy does not support is order verification. For example; verifying that your code opens a file before writing to it. It seemed to me to be a feature whose benefits are self-evident, but the Prophecy developers unfortunately did not agree.
So, fool that I am, I thought "How hard can it be?" and started work on Phony, a mocking framework designed to combine the strengths of its predecessors, and allow testing under HHVM without compromise.
New versions of PHP came along and introduced new language features, and Phony adapted to meet the requirements of testing these features. I was also fortunate enough to be part of a development team at my day job, who were willing to be the test bed for Phony, and it received a lot of real-world usage that contributed immensely to Phony's stability and eventual feature set.
Of course it turned into a much longer journey than I first expected, and Phony continues to be a challenging project to maintain. But for me, it's an invaluable tool that I use almost every day, and I hope it can be the same for you.
Thanks for listening.
Erin (@ezzatron)
Thanks
Special thanks to the following people:
- @jmalloc, @koden-km, @parkertr, and @darianbr for their invaluable help as test subjects.
- @szczepiq, and everyone who contributed to Mockito.
- @mlively, and everyone who contributed to Phake.
- @cjohansen, and everyone who contributed to Sinon.JS.
- @everzet, and everyone who contributed to Prophecy.
- @sebastianbergmann, and everyone who contributed to PHPUnit.
- @jails, and everyone who contributed to Kahlan.
License
For the full copyright and license information, please view the LICENSE file.
Note
This is a fork of the now unmaintained eloquent/phony. The original purpose of this fork was to allow for compatibility with PHP 8.3. Be aware that we do not intend to regularly maintain this other than to keep it up to date with PHP as needed.