PHPackages                             dancras/doubles - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. dancras/doubles

ActiveLibrary[Testing &amp; Quality](/categories/testing)

dancras/doubles
===============

A test doubles framework

1.0.0(13y ago)79711MITPHPPHP &gt;=5.3.0

Since Oct 16Pushed 13y ago1 watchersCompare

[ Source](https://github.com/dancras/Doubles)[ Packagist](https://packagist.org/packages/dancras/doubles)[ Docs](http://dancras.co.uk)[ RSS](/packages/dancras-doubles/feed)WikiDiscussions master Synced 4w ago

READMEChangelogDependenciesVersions (2)Used By (1)

Doubles
=======

[](#doubles)

Doubles is a php test doubles library with a goal to provide a simple, logical api and some syntactic sugar when writing unit tests. It is particularly suitable for the arrange-act-assert pattern of unit testing, although it doesn't intentionally impose any style. It has been designed with phpunit in mind, but has no dependencies and could have valid use cases with other testing frameworks. It is currently in the early stages of development and any feedback is very welcome.

Requires PHP 5.3+.

No changes to your unit testing framework are required for integration.

Copyright (c) 2012 Daniel Howlett

Dual licensed under the MIT and GPL licenses.

Feedback can be left at

[![Build Status](https://camo.githubusercontent.com/807ef8611281818d502f5e0a3e42a06b52ccd206932c9df6e92f9767a2089542/68747470733a2f2f7365637572652e7472617669732d63692e6f72672f64616e637261732f446f75626c65732e706e67)](http://travis-ci.org/dancras/Doubles)

Installation with composer
--------------------------

[](#installation-with-composer)

To install from packagist, add the following to your composer.json:

```
{
    "minimum-stability": "dev",
    "require": {
        "dancras/doubles": "*"
    }
}

```

Don't forget to include vendor/autoload.php in your code.

Known issues
------------

[](#known-issues)

- If your classes have matching methods to the chosen test double, there is currently no way to access the test double method.
- Failures triggered by the library will fail tests in phpunit with an E code rather than an F code.
- PHP 5.4 features eg. "callable" type hint are currently not supported.

Reference
---------

[](#reference)

Add the following:

```
use Doubles\Doubles;

```

### Full test doubles

[](#full-test-doubles)

Create test doubles with all methods of subject replaced:

```
$double = Doubles::fromClass('\MyClass');

$double = Doubles::fromInterface('\MyInterface');

```

Doubles will create the subject if it does not exist yet.

### Partial test doubles

[](#partial-test-doubles)

Methods of a partial test double are unaffected until they are stubbed, mocked or intercepted. They are created from an instance of the subject.

```
$subject = new \Doubles\Test\Dummy;

$double = Doubles::partial($subject);

```

It might be necessary to skip the constructor of a subject:

```
$subject = Doubles::noConstruct('\Doubles\Test\Dummy');

```

### Spies

[](#spies)

Provide access to the history of interactions with a test double, including unaffected methods of partial test doubles. A graph service test double might have the following actions performed on it:

```
$double->plot(0, 5);

$double->plot(2, 6);

$double->setLineColour('red');

$double->render();

```

We can interrogate the test double after the code is run:

```
$double->spy('plot')->args(0); // array(0, 5)

$double->spy('plot')->args(); // array(array(0, 5), array(2, 6))

$double->spy('plot')->findArgs(2, 6); // 1

$double->spy('plot')->arg(1, 0); // 2

$double->spy('plot')->callCount(); // 2

$double->callCount(); // 4

```

#### One Call

[](#one-call)

When a method is expecting one call we can avoid superfluous assertions by using the one call variant. Notice you can omit the call index when using the one call variant.

```
$double->spy('setLineColour')->oneCallArgs(); // array('red')

$double->spy('setLineColour')->oneCallArgs(0); // 'red'

```

A one call method will throw an exception if the method has not received exactly one call.

```
$double->spy('plot')->oneCallArgs(); // throws \Doubles\Core\FailureException

$double->spy('foo')->oneCallArgs(); // throws \Doubles\Core\FailureException

```

#### Call Order

[](#call-order)

Starts from one. Can be used to assert that methods are called in the expected order.

```
$double->spy('plot')->callOrder(0); // 1

$double->spy('plot')->callOrder(1); // 2

```

The one call variant also works for call order:

```
$double->spy('render')->oneCallOrder(0); // 4

```

The following code asserts that render is called last and only once:

```
$this->assertSame(
    $double->callCount(),
    $double->spy('render')->oneCallOrder(0)
); // pass

```

#### Shared Call Order

[](#shared-call-order)

Occasionally you need to compare the call order between instances. The Doubles\\Spy\\CallCounter::shareNew() method distributes a shared call counter. Assume all objects in this example have been created as test doubles:

```
use Doubles\Spy\CallCounter;

CallCounter::shareNew($pizza, $waiter, $customer);

```

The following actions are incorrectly performed on our objects. Our impatient customer seems to be helping him or herself:

```
$pizza->cook();

$customer->eat($pizza);

$waiter->take($pizza);

```

Using the shared call order we can catch this error in our tests. The pizza must be cooked before the waiter takes it:

```
$this->assertGreaterThan(
    $pizza->spy('cook')->oneSharedCallOrder(),
    $waiter->spy('take')->sharedCallOrder(0)
); // pass

```

The waiter must take the pizza before the customer eats it:

```
$this->assertGreaterThan(
    $waiter->spy('take')->sharedCallOrder(0),
    $customer->spy('eat')->sharedCallOrder(0)
); // fail

```

Notice again the one call variant, ensuring our pizza is not burnt.

### Stubs

[](#stubs)

```
$double->stub('foo', 'bar');

$double->foo(); // 'bar'

```

We can stub multiple methods at once, eg. to stub a fluent interface:

```
$double->stub('setX', 'setY', $double);

$double->setX(); // $double

$double->setY(); // $double

```

Stubs can also throw exceptions:

```
$double->stub('boom', new EndOfTheWorldException);

$double->boom(); // throws EndOfTheWorldException

```

To actually return an exception you need to use a mock.

### Mocks

[](#mocks)

Mocking is the most versatile way to test a method but can be difficult to follow.

```
$myObject->mock('give', function ($methodName, $arguments) use (&$m, &$a) {
    $m = $methodName; // 'give'
    $a = $arguments; // array(1, 2, 3)
    return 'result';
});

$myObject->give(1, 2, 3); // 'result'

```

Performing assertions within the mock callback is not recommended. If your code fails to call the method, no assertions will be run and the test may pass.

If you are asserting within the mock, you may want to use a spy. Alternatively, using variables by reference will allow you to perform your assertions outside the closure.

### Interceptors

[](#interceptors)

Intercepting is an improved form of mocking available to partials, providing the instance of the partial subject to the callback.

```
$myObject->intercept('foo', function ($methodName, $arguments, $instance) use (&$m, &$a) {
    $m = $methodName; // 'foo'
    $a = $arguments; // array(1, 2, 3)
    return $instances->foo($a);
});

$myObject->give(1, 2, 3); // 'result'

```

### Expectations

[](#expectations)

When you mock or stub a method it becomes expected. By default, calls to methods that are not expected have no repercussions.

```
$myObject->unknown(); // null

$myObject->setUnexpectedMethodCallback(function ($methodName, $arguments) {
    throw new Exception;
});

$myObject->unknown(); // throws Exception

```

### Rapid Prototyping

[](#rapid-prototyping)

By default, when using a test double for a defined type, an exception will be thrown if a method that doesn't exist on the original class or the test double API is called.

If the type doesn't exist then it is considered to be rapid prototyping (because having to define all your class signatures up front gets tedious). In this mode any methods can be used. When you define the class or interface, tests will fail until you complete it's signature.

I find this behaviour very effective, however if it is not desired functionality then rapid prototyping mode can be forced on, meaning methods outside the class or interface signature can be used freely.

```
\Doubles\Core\TestDouble::$isRapidPrototypingEnabled = true;

```

###  Health Score

29

—

LowBetter than 60% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity16

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

4952d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4f48133ba754279863f4c6118e819292a4699cdfdb31f49b3ac098179a84c9d1?d=identicon)[dancras](/maintainers/dancras)

---

Top Contributors

[![dancras](https://avatars.githubusercontent.com/u/650662?v=4)](https://github.com/dancras "dancras (50 commits)")[![ithinkihaveacat](https://avatars.githubusercontent.com/u/51244?v=4)](https://github.com/ithinkihaveacat "ithinkihaveacat (2 commits)")

---

Tags

testingmockstubtest double

### Embed Badge

![Health badge](/badges/dancras-doubles/health.svg)

```
[![Health](https://phpackages.com/badges/dancras-doubles/health.svg)](https://phpackages.com/packages/dancras-doubles)
```

###  Alternatives

[mockery/mockery

Mockery is a simple yet flexible PHP mock object framework

10.7k497.0M23.4k](/packages/mockery-mockery)[phpspec/prophecy

Highly opinionated mocking framework for PHP 5.3+

8.5k551.7M677](/packages/phpspec-prophecy)[php-mock/php-mock

PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.

36918.1M97](/packages/php-mock-php-mock)[php-mock/php-mock-phpunit

Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.

1718.2M395](/packages/php-mock-php-mock-phpunit)[phake/phake

The Phake mock testing library

4758.0M322](/packages/phake-phake)[kahlan/kahlan

The PHP Test Framework for Freedom, Truth and Justice.

1.2k1.2M245](/packages/kahlan-kahlan)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
