PHPackages                             timacdonald/callable-fake - 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. timacdonald/callable-fake

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

timacdonald/callable-fake
=========================

A testing utility that allows you to fake and capture invokations of a callable / Closure

v1.9.0(1y ago)4519.5k↑16.7%23MITPHPPHP ^8.2CI failing

Since Apr 5Pushed 1y ago1 watchersCompare

[ Source](https://github.com/timacdonald/callable-fake)[ Packagist](https://packagist.org/packages/timacdonald/callable-fake)[ RSS](/packages/timacdonald-callable-fake/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (9)Dependencies (1)Versions (12)Used By (3)

[![Callable Fake: a PHP package by Tim MacDonald](/art/header.png)](/art/header.png)

Callable / Closure testing fake
===============================

[](#callable--closure-testing-fake)

If you have an interface who's public API allows a developer to pass a Closure / callable, but causes no internal or external side-effects, as these are left up to the developer using the interface, this package may assist in testing. This class adds some named assertions which gives you an API that is very much inspired by Laravel's service fakes. It may be a little more verbose, but it changes the language of the tests to better reflect what is going on.

It also makes it easy to assert the order of invocations, and how many times a callable has been invoked.

Installation
------------

[](#installation)

You can install the package using [composer](https://getcomposer.org/):

```
composer require timacdonald/callable-fake --dev

```

Basic usage
-----------

[](#basic-usage)

This packge requires you to be testing a pretty specfic type of API / interaction to be useful. Imagine you are developing a package that ships with the following interface...

```
interface DependencyRepository
{
    public function each(callable $callback): void;
}
```

This interface accepts a callback, and under the hood loops through all "dependecies" and passes each one to the callback for the developer to work with.

### Before

[](#before)

Let's see what the a test for this method might look like...

```
public function testEachLoopsOverAllDependencies(): void
{
    // arrange
    $received = [];
    $expected = factory(Dependency::class)->times(2)->create();
    $repo = $this->app[DependencyRepository::class];

    // act
    $repo->each(function (Dependency $dependency) use (&$received): void {
        $received[] = $dependency;
    });

    // assert
    $this->assertCount(2, $received);
    $this->assertTrue($expected[0]->is($received[0]));
    $this->assertTrue($expected[1]->is($received[1]));
}
```

### After

[](#after)

```
public function testEachLoopsOverAllDependencies(): void
{
    // arrange
    $callable = new CallableFake();
    $expected = factory(Dependency::class)->times(2)->create();
    $repo = $this->app[DependencyRepository::class];

    // act
    $repo->each($callable);

    // assert
    $callable->assertTimesInvoked(2);
    $callable->assertCalled(function (Depedency $dependency) use ($expected): bool {
        return $dependency->is($expected[0]);
    });
    $callable->assertCalled(function (Dependency $dependency) use ($expected): bool {
        return $dependency->is($expected[1]);
    });
}
```

Available assertions
--------------------

[](#available-assertions)

All assertions are chainable.

### assertCalled(callable $callback): self

[](#assertcalledcallable-callback-self)

```
$callable->assertCalled(function (Dependency $dependency): bool {
    return Str::startsWith($dependency->name, 'spatie/');
});
```

### assertNotCalled(callable $callback): self

[](#assertnotcalledcallable-callback-self)

```
$callable->assertNotCalled(function (Dependency $dependency): bool {
    return Str::startsWith($dependency->name, 'timacdonald/');
});
```

### assertCalledIndex(callable $callback, int|array $index): self

[](#assertcalledindexcallable-callback-intarray-index-self)

Ensure the callable was called in an explicit order, i.e. it was called as the 0th and 5th invocation.

```
$callable->assertCalledIndex(function (Dependency $dependency): bool {
    return Str::startsWith($dependency, 'spatie/');
}, [0, 5]);
```

### assertCalledTimes(callable $callback, int $times): self

[](#assertcalledtimescallable-callback-int-times-self)

```
$callable->assertCalledTimes(function (Dependency $dependency): bool {
    return Str::startsWith($dependency, 'spatie/');
}, 999);
```

### assertTimesInvoked(int $times): self

[](#asserttimesinvokedint-times-self)

```
$callable->assertTimesInvoked(2);
```

### assertInvoked(): self

[](#assertinvoked-self)

```
$callable->assertInvoked();
```

### assertNotInvoked(): self

[](#assertnotinvoked-self)

```
$callable->assertNotInvoked();
```

Non-assertion API
-----------------

[](#non-assertion-api)

### asClosure(): Closure

[](#asclosure-closure)

If the method is type-hinted with `\Closure` instead of callable, you can use this method to transform the callable to an instance of `\Closure`.

```
$callable = new CallableFake;

$thing->closureTypeHintedMethod($callable->asClosure());

$callable->assertInvoked();
```

### wasInvoked(): bool

[](#wasinvoked-bool)

```
if ($callable->wasInvoked()) {
    //
}
```

### wasNotInvoked(): bool

[](#wasnotinvoked-bool)

```
if ($callable->wasNotInvoked()) {
    //
}
```

### called(callable $callback): array

[](#calledcallable-callback-array)

```
$invocationArguments = $callable->called(function (Dependency $dependency): bool {
    return Str::startsWith($dependency->name, 'spatie/')
});
```

Specifying return values
------------------------

[](#specifying-return-values)

If you need to specify return values, this *could* be an indicator that this is not the right tool for the job. But there are some cases where return values determine control flow, so it can be handy, in which case you can pass a "return resolver" to the named constructor `withReturnResolver`.

```
$callable = CallableFake::withReturnResolver(function (Dependency $dependency): bool {
    if ($dependency->version === '*') {
        return '🤠';
    }

    return '😀';
});

// You would not generally be calling this yourself, this is simply to demonstate
// what will happen under the hood...

$emoji = $callable(new Dependecy(['version' => '*']));

// $emoji === '🤠';
```

Credits
-------

[](#credits)

- [Tim MacDonald](https://github.com/timacdonald)
- [All Contributors](../../contributors)

And a special (vegi) thanks to [Caneco](https://twitter.com/caneco) for the logo ✨

Thanksware
----------

[](#thanksware)

You are free to use this package, but I ask that you reach out to someone (not me) who has previously, or is currently, maintaining or contributing to an open source library you are using in your project and thank them for their work. Consider your entire tech stack: packages, frameworks, languages, databases, operating systems, frontend, backend, etc.

###  Health Score

47

—

FairBetter than 94% of packages

Maintenance44

Moderate activity, may be stable

Popularity37

Limited adoption so far

Community18

Small or concentrated contributor base

Maturity75

Established project with proven stability

 Bus Factor1

Top contributor holds 93.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

Every ~200 days

Recently: every ~278 days

Total

10

Last Release

434d ago

PHP version history (5 changes)v1.0.0PHP ^7.1

v1.3.0PHP ^7.1 || ^8.0

v1.6.0PHP ^8.1 || ^8.2

v1.7.0PHP ^8.1

v1.9.0PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/24803032?v=4)[Tim MacDonald](/maintainers/timacdonald)[@timacdonald](https://github.com/timacdonald)

---

Top Contributors

[![timacdonald](https://avatars.githubusercontent.com/u/24803032?v=4)](https://github.com/timacdonald "timacdonald (123 commits)")[![dependabot-preview[bot]](https://avatars.githubusercontent.com/in/2141?v=4)](https://github.com/dependabot-preview[bot] "dependabot-preview[bot] (5 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (3 commits)")[![caneco](https://avatars.githubusercontent.com/u/502041?v=4)](https://github.com/caneco "caneco (1 commits)")

---

Tags

callableclosurehacktoberfestphptestingtestingcallableclosurefake

### Embed Badge

![Health badge](/badges/timacdonald-callable-fake/health.svg)

```
[![Health](https://phpackages.com/badges/timacdonald-callable-fake/health.svg)](https://phpackages.com/packages/timacdonald-callable-fake)
```

###  Alternatives

[brianium/paratest

Parallel testing for PHP

2.5k118.8M754](/packages/brianium-paratest)[orchestra/testbench

Laravel Testing Helper for Packages Development

2.2k39.1M32.1k](/packages/orchestra-testbench)[timacdonald/log-fake

A drop in fake logger for testing with the Laravel framework.

4235.9M56](/packages/timacdonald-log-fake)[typo3/testing-framework

The TYPO3 testing framework provides base classes for unit, functional and acceptance testing.

675.0M775](/packages/typo3-testing-framework)[robiningelbrecht/phpunit-pretty-print

Prettify PHPUnit output

76460.0k15](/packages/robiningelbrecht-phpunit-pretty-print)[juampi92/test-seo

Easy way to test your SEO

26341.0k](/packages/juampi92-test-seo)

PHPackages © 2026

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