• This repository has been archived on 08/Jul/2023
  • Stars
    star
    194
  • Rank 200,171 (Top 4 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 10 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Mocks, stubs, and spies for PHP.

No longer maintained

This package is no longer maintained. See this statement for more info.

Phony

Mocks, stubs, and spies for PHP.

Current version image

Example verification output

Installation

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

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:

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:

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:

Example 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:

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 eloquent/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 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');
        $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:

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:

License

For the full copyright and license information, please view the LICENSE file.

More Repositories

1

enumeration

An enumeration implementation for PHP.
PHP
147
star
2

composer-npm-bridge

NPM integration for Composer packages.
PHP
78
star
3

pathogen

General-purpose path library for PHP.
PHP
76
star
4

otis

One-time password / multi-factor authentication library for PHP.
PHP
40
star
5

asplode

Drop-in exception-based error handling for PHP.
PHP
38
star
6

mooch

A simple Twitter OAuth proxy.
CoffeeScript
35
star
7

liberator

A proxy for circumventing PHP access modifier restrictions.
PHP
34
star
8

composer-config-reader

A light-weight component for reading Composer configuration files.
PHP
32
star
9

lockbox-php

Simple, strong encryption.
PHP
24
star
10

php-lcs

An implementation of the 'longest common subsequence' algorithm for PHP.
PHP
16
star
11

confetti

Streaming data transformation system for PHP.
PHP
15
star
12

pops

PHP object proxy system
PHP
15
star
13

dumpling

Diff-friendly mysqldump with an improved interface.
PHP
14
star
14

typhax

A flexible PHP type hinting syntax.
PHP
13
star
15

cosmos

A library for representing and manipulating PHP class names.
PHP
13
star
16

equality

A better strict comparison for PHP.
PHP
13
star
17

pbkdf2-compat

A compatibility library for the PHP 5.5 function hash_pbkdf2().
PHP
13
star
18

schemer

Generic schema validation library for PHP, based on JSON Schema.
PHP
12
star
19

liftoff

Open any file or URI using the default GUI application from within PHP.
PHP
12
star
20

github-release-action

A GitHub Action that creates GitHub Releases from your Git tags. Does what you probably wish GitHub would just do without the need to use GitHub Actions.
JavaScript
11
star
21

endec

Versatile encoding implementations for PHP.
PHP
9
star
22

fixie

YAML-based data fixtures.
PHP
8
star
23

lockbox-nodejs

Simple, strong encryption.
CoffeeScript
7
star
24

phony-peridot

Phony for Peridot.
PHP
6
star
25

phony-phpunit

Phony for PHPUnit.
PHP
6
star
26

blox

A parser for block comment documentation.
PHP
6
star
27

precis-js

A JavaScript implementation of RFC 7564 (The PRECIS Framework).
CoffeeScript
6
star
28

couchdb-builder

A tool for building CouchDB documents from flat files.
CoffeeScript
6
star
29

typhoon

Simple, flexible type-checking for PHP.
PHP
5
star
30

constance

PHP constants as enumerations.
PHP
5
star
31

phpstan-phony

PHPStan configuration for Phony.
PHP
5
star
32

phony-kahlan

Phony for Kahlan.
PHP
5
star
33

phpunit-extensions

Extensions for PHPUnit to provide additional functionality.
PHP
3
star
34

git-version-webpack-plugin

Pull Git version information into Webpack.
JavaScript
3
star
35

depict

A fast, recursion-safe, and succinct replacement for var_dump/var_export/print_r.
PHP
2
star
36

lockbox

Simple, strong encryption.
HTML
1
star
37

lockbox-java

Simple, strong encryption.
Java
1
star