PHPackages                             filisko/testable-functions - 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. filisko/testable-functions

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

filisko/testable-functions
==========================

A library for testing PHP's built-in functions or language constructs.

v1.3.1(5mo ago)3411MITPHPPHP &gt;=7.1CI passing

Since May 4Pushed 5mo agoCompare

[ Source](https://github.com/filisko/testable-functions)[ Packagist](https://packagist.org/packages/filisko/testable-functions)[ Docs](https://github.com/filisko/testable-functions)[ RSS](/packages/filisko-testable-functions/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (3)Versions (6)Used By (0)

Testable PHP functions
======================

[](#testable-php-functions)

[![Latest Version on Packagist](https://camo.githubusercontent.com/2e7c25c793ab4fd60590a48bf1507f27a7b5227b6441e2ec4d6870ad950fcc01/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f66696c69736b6f2f7465737461626c652d66756e6374696f6e732e7376673f7374796c653d666c6174)](https://packagist.org/packages/filisko/testable-functions)[![Software License](https://camo.githubusercontent.com/011ab289702ea32abcf53bc0d330758d85023ed63c970663774b31db27aef51e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d696e666f726d6174696f6e616c2e7376673f7374796c653d666c6174)](LICENSE)[![Testing](https://github.com/filisko/testable-functions/workflows/testing/badge.svg)](https://github.com/filisko/testable-functions/workflows/testing/badge.svg)[![Coverage Status](https://camo.githubusercontent.com/ba1450a778a9006e081c4d5abd04f03823f29bc1d19641e51b87f71e555688bb/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f66696c69736b6f2f7465737461626c652d66756e6374696f6e732f62616467652e7376673f6272616e63683d6d61696e)](https://camo.githubusercontent.com/ba1450a778a9006e081c4d5abd04f03823f29bc1d19641e51b87f71e555688bb/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f66696c69736b6f2f7465737461626c652d66756e6374696f6e732f62616467652e7376673f6272616e63683d6d61696e)[![Total Downloads](https://camo.githubusercontent.com/1e6e6b5241c68055adf5b18d1c95e8b456bf687556c3cc6fa56b759288fa914a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f66696c69736b6f2f7465737461626c652d66756e6374696f6e732e7376673f7374796c653d666c6174)](https://packagist.org/packages/filisko/testable-functions)

A library that provides an approach for testing code that heavily relies on PHP's built-in functions or language constructs (great for "include/require-oriented architectures", such as legacy projects) that are normally really hard to test.

There are no excuses for not testing PHP functions anymore!

Requirements
------------

[](#requirements)

- PHP &gt;= 7.1

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

[](#installation)

This package is installable and autoloadable via Composer as [filisko/testable-functions](https://packagist.org/packages/filisko/testable-functions).

```
composer require filisko/testable-functions
```

Usage
-----

[](#usage)

The package provides two main classes: `Functions` used in production and `FakeFunctions` used for testing.

These two classes allow you to use PHP's built-in functions and language constructs (require\_once, include, echo, print, etc.) without having to worry about tests.

You can see a basic [example here](tests/Examples/Email) of production code and its tests, along with many comments to make the example clearer.

### Functions class

[](#functions-class)

This class is like a proxy for PHP functions. It uses the `__call` hook internally to forward function calls to PHP, and it also wraps PHP's language constructs like `require_once` inside functions so that they can be testable.

Using this class can be particularly useful for code that involves I/O operations because the results of those operations can be easily altered for testing purposes later on.

Imagine the following production code:

```
// this is passed to the constructor of the client class
$functions = new \Filisko\Functions();

// filesystem
$functions->file_exists($path);
$functions->is_dir($dirname);
$functions->is_file($filename);

// network
$functions->checkdnsrr($hostname);
$functions->fsockopen($hostname);

// clock
$functions->time();
$functions->date_create();
```

Then, by using the `FakeFuctions` class in the testing environment, the results of the functions can be easily altered like this:

```
// ----- inside the PHPUnit test case -----
use PHPUnit\Framework\Assert;

// this is the default value, and it means that
// undefined functions will fall back to their implementation
$failOnMissing = false;

$functions = new \Filisko\FakeFunctions([
    // if you don't add it here, it will fallback to PHP
    'time' => 1417011228,
    'date_create' => new DateTime('2025-05-15'),
    'is_dir' => function(string $path)  {
        // you can assert arguments here
        Assert::assertEquals('/path/to/dir', $path);

        return false;
    },
], $failOnMissing);

$fileManager = new FileManager($functions);

$this->assertEquals(false, $client->do());

// ------- inside the class under test -------

// returns 1417011228
$functions->time();

// returns DateTime('2025-05-15')
$functions->date_create();

// returns false
$functions->is_dir($dirname);
```

Legacy projects are usually "require/include oriented architectures", so the following can be very handy.

As you've read before, this package supports PHP language constructs (parsed differently than functions by PHP) wrapped in functions:

```
$functions->require_once($path);
$functions->require($path);
$functions->include_once($path);
$functions->include($path);
$functions->echo($text);
$functions->print($text);
$functions->exit($statusOrText);
$functions->die($statusOrText);
```

This makes it possible to alter them for testing purposes:

```
// ---------- inside a PHP Unit test ----------

$functions = new \Filisko\FakeFunctions([
    // simulating a file loading global vars
    'require_once' => function() {
        // you should never do this unless you're testing legacy code
        global $var
        $var = 1;
    },
    // you can also load anything (functions, classes, etc.) from a file
    'require' => function() {
        eval(file_get_contents(__DIR__ . '/functions.php'));
    },
    // simulating a file returning a value
    'include' => false,
]);

// ------- inside the class under test --------

// $var now is available
$functions->require_once($path);
global $var;

// returns false
$functions->include($dirname);
```

Keep in mind that loading things like globals will make them available across all the other tests, and you may not want this. To solve this issue, use PHPUnit's docblocks for each test method shown below:

- globals (`@backupGlobals`)
- classes or functions (`@runInSeparateProcess`, `@preserveGlobalState disabled`)
- static variables or properties (`@backupStaticAttributes`)

... or [at class level with `@runTestsInSeparateProcesses`](https://docs.phpunit.de/en/10.5/annotations.html#runtestsinseparateprocesses).

```
/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function test_with_functions_loading(): void
{
    // ...
}
```

Furthermore, passing `--process-isolation` to PHPUnit will globally apply `@runInSeparateProcess` to every single test, but it's not a good idea to do it unless you know what you're doing.

### FakeFunctions class

[](#fakefunctions-class)

As shown in the previous examples, this class is used as a replacement for the `Functions` class in the testing environment, but it also provides many helper methods for the tests.

```
$functions = new \Filisko\FakeFunctions([
    // any value  (can only be used once, otherwise an exception will be thrown)
    'some_function' => true,

    // callables (can only be used once, otherwise an exception will be thrown)
    'some_function' => function() {
        return true;
    },

    // this will return the same result no matter how many times it's called
    'some_function' => new FakeStatic($mixed),

    // a stack of values that will be used for each function call
    // it throws an exception when the stack is already consumed
    'some_function' => new FakeStack([true, false, 1, 2]),
]);

// it's also possible to preset the values using the set() method,
// this allows you to postpone the setting of a function's preset result until it is needed.
$functions->set('some_function', true);
$functions->set('some_function', new FakeStatic($mixed));
$functions->set('some_function', new FakeStack([true, false, 1, 2]));

// We can adjust whether we want to throw an exception when a result for a function is not set,
// yet the function was called anyway (like an unexpected call).
// This configuration defaults to false, which causes a fallback to PHP's native functions when a mock was not set.
// On the other hand, enabling it can be very useful to make sure that only expected calls are made.
// (e.g.: avoid unexpected DB calls in legacy code or external HTTP requests)
$failOnMissing = true;
$functions = new \Filisko\FakeFunctions([
    // this is used to fallback to the native implementation
    // even though $failOnMissing is enabled (it will fail otherwise)
    'mail' => new FakeFallback(),

    // if a call is made and not mocked here, it will fail
    'other_function' => true
], $failOnMissing);

// returns a bool of whether a function was called or not
$functions->wasCalled('require_once');

// returns an int of the number of times that a function was called
$functions->wasCalledTimes('require_once');

// returns a list (array) of function names together with the pending calls
// e.g.: [ 'function_exists' => 2 ]
$functions->pendingCalls();

// returns an int for the pending calls of a specific function (it will throw an exception for non-defined function calls)
$functions->pendingCalls('filter_var');

// returns the total of all the pending calls
// (this can be used to assert that all the values were consumed by the end of the test)
$functions->pendingCallsCount();

// returns an array of all the calls together with the arguments of each call
// the example below is the result of two calls for the same function with different argument each time
// e.g.: [ 'require_once' => [['file.php'], ['test.php']] ]
$functions->calls();

// returns an array of calls for one specific function
// e.g.: [['file.php'], ['test.php']]
$functions->calls('require_once');
// e.g.: ['test.php']
$functions->calls('require_once')[1];

// returns the first call of a function (throws an exception if it wasn't called yet)
// e.g.: ['argument']
$functions->first('filter_var');
// e.g.: 'argument'
$functions->first('filter_var')[0];

// returns the first argument of the first function call (throws an exception if it wasn't called yet)
// e.g.: 'argument'
$functions->firstArgument('filter_var');

// returns the list of returned results of a 'real function call' (a function that actually run != mocked value)
// it won't return the result of mocked/preset function results or language constructs like require, echo, etc.
// e.g.: ['fecbada3-0c27-47ce-addf-f840050ee204', '...']
$functions->results('uuid');
$functions->results('uuid')[0];

// the same as results() but it only returns the last result of the function call
// this is useful when you want to avoid hardcoding a result just so that you have control over it.
// this way, you let the code run and only get the result of the function to do something more with it (e.g.: an ID)
// e.g.: 'fecbada3-0c27-47ce-addf-f840050ee204'
$functions->lastResult('uuid');
// you can also specify the number of result you want to get starting from the end
// 0 is last (default), 1 is penultimate, etc.
$functions->lastResult('uuid', 1);

// returns an array of string[] of all the echos
$functions->echos();

// returns a bool of whether the string was echoed or not
$functions->wasEchoed('Was I echoed?');

// returns an array of string[] of all the prints
$functions->prints();

// returns a bool of whether the string was printed or not
$functions->wasPrinted('Was I printed?');

// returns a bool of whether die() was called or not
$functions->died();

// returns the die code or string that was passed to die($status)
$functions->dieCode();

// returns a bool of whether exit() was called or not
$functions->exited();

// returns the exit code or string that was passed to exit($status)
$functions->exitCode();

// returns a bool of whether a file was included or not
$functions->wasIncluded('file.php');

// returns a bool of whether a file was included once or not
$functions->wasIncludedOnce('file.php');

// returns a bool of whether a file was required or not
$functions->wasRequired('file.php');

// returns a bool of whether a file was required once or not
$functions->wasRequiredOnce('file.php');
```

Why to use this package?
------------------------

[](#why-to-use-this-package)

Why to choose this package over a hundred other tools out there?

Because we prefer simplicity over complicated mocking tools, and we just want enough to accomplish our goal.

This is a common example from other mocking tools:

```
$builder = new MockBuilder();
$builder->setNamespace(__NAMESPACE__)
        ->setName("time")
        ->setFunction(
            function () {
                return 1417011228;
            }
        );

$mock = $builder->build()

$result = $mock->time();
```

While for us it's simply:

```
$functions = new FakeFunctions([
    'time' => 1417011228
]);

$result = $functions->time();
```

Cons
----

[](#cons)

### Injecting `Functions` into the class.

[](#injecting-functions-into-the-class)

For some, this is a con; for us, it's simply how it should be. We follow the DI principle for almost everything, so why not for this?

We consider it a good practice to always set the `Functions` dependency in the constructor as the last one, so that "it doesn't get in our way" and also set it as default so that nothing needs to be passed to the constructor in production. Only when testing.

```
class Filesystem
{
    public function __construct(
        private SomeService $service,
        private Functions $functions = new Functions()
    ) {}
}
```

### PHP functions autocomplete is lost in IDEs

[](#php-functions-autocomplete-is-lost-in-ides)

This is a natural consequence of this lib's approach. PHP functions autocomplete is lost given that we use a 'Proxy' class.

However, we developed a tool that generates a 'Stub' file with all the PHP functions so that your IDE keeps the autocomplete for them.

All you have to do is run the following command:

```
./vendor/filisko/testable-functions/bin/generate_stub.php [optinal path folder, by default it's /.phpstorm-stubs]
```

Keep in mind that the functions that it loads in the Stub file are based on the active PHP extensions at runtime and loaded by composer.

Using this tool is optional, but it's recommended.

Other testing utilities
-----------------------

[](#other-testing-utilities)

- PSR-3 fake logger: [filisko/fake-psr3-logger](https://github.com/filisko/fake-psr3-logger)
- PSR-15 middleware dispatcher: [middlewares/utils](https://github.com/middlewares/utils?tab=readme-ov-file#dispatcher) (used in conjuction with PSR-7 and PSR-17)
- PSR-16 fake cache: [kodus/mock-cache](https://github.com/kodus/mock-cache)

---

License and Contribution
------------------------

[](#license-and-contribution)

Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details.

The MIT License (MIT). Please see [LICENSE](LICENSE) for more information.

###  Health Score

35

—

LowBetter than 79% of packages

Maintenance71

Regular maintenance activity

Popularity19

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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 ~53 days

Total

5

Last Release

163d ago

PHP version history (2 changes)v1.0.0PHP &gt;=7.2

v1.1.0PHP &gt;=7.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/42e0d72f42eb7d84f67e20d28606da42e5a3248ca908b1eadb4366aafeae2561?d=identicon)[filisko](/maintainers/filisko)

---

Top Contributors

[![filisko](https://avatars.githubusercontent.com/u/8798694?v=4)](https://github.com/filisko "filisko (155 commits)")

---

Tags

phptestingunitmockfakefunctionsintegrationnativetestable

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/filisko-testable-functions/health.svg)

```
[![Health](https://phpackages.com/badges/filisko-testable-functions/health.svg)](https://phpackages.com/packages/filisko-testable-functions)
```

###  Alternatives

[php-vfs/php-vfs

Virtual file system implementation for use with PHP unit testing.

572.7M24](/packages/php-vfs-php-vfs)[icecave/isolator

Dependency injection for global functions.

371.3M29](/packages/icecave-isolator)[quizlet/hammock

Hammock is a stand-alone mocking library for Hacklang.

27445.5k](/packages/quizlet-hammock)[doppiogancio/mocked-client

A simple way to mock a client

2174.9k3](/packages/doppiogancio-mocked-client)[phpcurl/curlwrapper

The simplest OOP wrapper for curl, curl\_multi, curl\_share functions. Use CURL as a dependency, not hard-coded!

11110.4k3](/packages/phpcurl-curlwrapper)

PHPackages © 2026

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