# Kahlan is a PHP test framework that monkey patches without a PECL extension

> Kahlan is an MIT licensed PHP test framework with a describe-it syntax, built-in stubbing and monkey patching that needs no compiled extension, code coverage through phpdbg or Xdebug, and reporters and exporters for istanbul, lcov, Coveralls and Clover. It targets PHP 7.4 and above, and the 6.1.x line is shipping regular patch releases.

**kahlan/kahlan** — :heavy_check_mark: PHP Test Framework for Freedom, Truth, and Justice

- Repository: https://github.com/kahlan/kahlan
- Website: https://kahlan.github.io/docs/
- Stars: 1,146 · Forks: 70
- Language: PHP
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/kahlan-kahlan

## describe and it, with expectations that read like sentences

Kahlan describes itself as a full-featured Unit and BDD test framework a la RSpec/JSpec which uses a describe-it syntax and moves testing in PHP one step forward. The whole syntax is visible in the README's example.

```php
<?php

describe("Example", function() {

    it("makes an expectation", function() {

         expect(true)->toBe(true);

    });

});
```

A describe block is a closure with a name, an it block is a closure inside it, and the assertion is expect(value)->toBe(expected). The result is that a specification file reads as a set of sentences rather than as a set of method calls, and the failure output can quote the it description as the name of the failing case.

The name matters. Kahlan is described as a PHP Test Framework for Freedom, Truth, and Justice, and the describe-it structure is the point of it: a specification is a document first and a test suite second, and the test runner is one report format for that document.

The topics the repository carries make the intent clear, with bdd, tdd, spec and test alongside coverage and the one that stands out, monkey-patching. That last tag is the differentiator, and it is the subject of the next section.

## Monkey patching works without a compiled extension

The README's headline claim is specific and checkable. Kahlan lets you stub or monkey patch your code directly like in Ruby or JavaScript without any required PECL-extensions.

The reason this is unusual in PHP is that the language has no built-in way to replace an existing function or class at runtime. Ruby and JavaScript do, so a test can substitute a collaborator and then assert on the call. In PHP, historically, the way to get that behaviour was a compiled extension, which meant an installation step that many CI images and shared hosting environments would not have.

Kahlan removes that dependency, and the feature list spells out what it buys. There is the ability to monkey patch your code, described as allowing replacement of core functions and classes on the fly, and there is the ability to set stubs on your class methods directly, described as allowing dynamic mocking. There is also the ability to check called methods on your classes and instances, which is the assertion half of the story, since a stub that records its calls is what lets you write expect($user)->toReceive('save').

The rest of the list is standard test framework furniture. RSpec and JSpec syntax, code coverage metrics, built-in reporters, built-in exporters, and an extensible, customizable workflow.

The practical consequence is a test suite that can reach inside things. That is genuinely useful when the code under test was not written to be testable, and it is also the reason the feature is controversial, which is a subject for later.

## allow, toBeCalled and toReceive are the three verbs of the stubbing API

The README's syntax block contains four examples that together cover the stubbing surface, and they are worth reading one at a time because the verbs are specific.

The first stubbing example stubs a function.

```php
it("stubs a function", function() {

    allow('time')->toBeCalled()->andReturn(123);
    $user = new User();
    expect($user->save())->toBe(true)
    expect($user->created)->toBe(123);

});
```

allow names the target, toBeCalled declares the expectation that it is called, and andReturn fixes the value. Here the target is the global function time, and the test then asserts both that save returned true and that the created timestamp equals 123, which is the assertion you could not write without the stub.

The second stubs a class, and the class is a core one.

```php
it("stubs a class", function() {

    allow('PDO')->toReceive('prepare', 'fetchAll')->andReturn([['name' => 'bob']]);
    $user = new User();
    expect($user->all())->toBe([['name' => 'bob']]);

});
```

toReceive takes method names, here prepare and fetchAll, and andReturn gives the value those calls return. Replacing PDO wholesale is the kind of thing that makes a database-backed class testable without a database.

The third example is the expectation side.

```php
it("expects methods to be called", function() {

    $user = new User();
    expect($user)->toReceive('save')->with(['validates' => false]);
    $user->save(['validates' => false]);

});
```

toReceive declares that a method is expected, and with constrains the arguments. The README also states the point of the whole system in a line, that mockery and prophecy are no longer needed, since the stubbing system is built in.

## Coverage is the only thing that needs a driver

The requirements are short. PHP 7.4 or later, Composer, and phpdbg or Xdebug, where the parenthetical says only required for code coverage analysis.

That parenthetical is the important part. Everything else in the framework runs on a stock PHP installation, and the driver is only needed if you want the coverage metrics. The feature list repeats the point, listing code coverage metrics with xdebug or phpdbg required.

There is a reason both are offered. Xdebug is an extension that has to be installed and configured, while phpdbg is a separate SAPI that ships with PHP itself for command line debugging, and the latter is the cheaper option for a CI job that only needs coverage. Either way it is a one-time setup rather than a hard requirement.

The installation path is Composer, and it is a dev dependency.

```bash
$ composer require --dev kahlan/kahlan
```

The project uses Semantic Versioning and maintains a CHANGELOG to help you easily understand what's happening, which is a courtesy many test frameworks skip and all of them should provide.

The second install path is a git clone, and it is worth looking at because it shows the runner in use.

```bash
git clone git://github.com/kahlan/kahlan.git
cd kahlan
composer install
bin/kahlan              # to run specs or,
bin/kahlan --coverage=4 # to run specs with coverage info for namespaces, classes & methods (require xdebug)
```

The coverage flag takes a number, and the comment says what that number means: coverage info for namespaces, classes and methods, and that this one requires xdebug. The default invocation with no arguments runs the specs, and the runner is a bin/kahlan script rather than a Composer script alias.

## Reporters and exporters cover the usual CI reporting targets

The output side of the framework is described in one dense line of the feature list. There are built-in reporters, either Terminal or HTML reporting through istanbul or lcov, and built-in exporters for Coveralls, Code Climate, Scrutinizer and Clover.

The split between reporters and exporters is worth understanding, because it is not a distinction every framework makes. A reporter decides how results are shown to a person, and the choice here is a terse dot reporter or a verbose one, both of which the README illustrates with screenshots, and an HTML output produced through istanbul or lcov. An exporter decides how results are handed to a third-party service, and those four are the hosted code coverage and CI analysis services a PHP project typically wants to post to.

That means the coverage artefact can be produced in the format one of those services expects, without an adapter. The HTML report is generated from the same istanbul or lcov output, so a team that wants a browsable coverage page and a team that wants a Coveralls badge are using the same underlying run.

The repository structure backs this up. There is a phpcs.xml at the root, which is the coding standard configuration for the project itself, and a rector.php, which is the configuration for Rector, the automated refactoring tool. A framework that ships a coding standard and a refactoring configuration is a project that expects its own code to be held to the standard it asks you to use.

There is also kahlan-config-github-action.php at the root, which is the configuration file for running Kahlan in a GitHub Actions workflow, and that is the piece to look at when you set up continuous integration.

## The dev branch is the default, and the 6.1 line moves often

The default branch is dev, and the CI and coverage badges in the README are both pinned to it, the build badge pointing at the Build workflow and the codecov badge at the dev branch. That is a normal arrangement for a package that develops on a development branch and releases from it, but it means the branch you see in the repository is the moving target rather than a released state.

The release record is short and regular. The recent releases are 6.1.3 on 2026-10-13, 6.1.2 on 2026-09-28 and 6.1.1 on 2026-07-21, so three patch releases in under three months, which for a framework is a healthy sign of an active maintenance burden rather than an abandoned project. The repository is not archived, and the last push was on 2026-10-19.

The licence is MIT, with the LICENSE.txt file at the repository root, and the package is published on Packagist, which the badges point to. You can use, modify and redistribute it including commercially, provided the copyright notice and the licence text are retained, and that is a licensing fact rather than legal advice.

The other top-level entries describe the shape of the project. There is src/ for the implementation, spec/ for the framework's own test suite, which is the suite that runs the framework's tests, bin/ for the runner and other executables, docs/ for the documentation sources, composer.json for the package metadata, autoload.php, and the CHANGELOG.md.

The documentation is published at kahlan.github.io/docs, and the README also points to two conference talks, a 2015 Unified Diff presentation and a 2016 French tutorial, plus an IRC channel on chat.freenode.net. The IRC reference is a good dating signal: freenode is not where a live project would point today, so the README's community section has not been revisited in some time even though the code has been.

## The alternative is PHPUnit with a separate mocking library

The realistic alternative for a PHP team is a PHPUnit-style suite with Mockery or Prophecy for the mocking, which is exactly the pairing Kahlan's feature list contrasts itself against when it says mockery and prophecy are no longer needed.

The difference in approach is packaging rather than capability. With the separate-libraries route you have a runner, an assertion library and a mocking library, each with its own configuration, and the mocking conventions are the mocking library's. With Kahlan you have one framework that owns the specification syntax, the assertions, the stubbing and the monkey patching, so the whole test is described in one vocabulary.

The other difference is the monkey patching itself, and this is where the trade-off bites. The ability to replace a core function or class on the fly is Kahlan's most distinctive feature and also its most dangerous one. It means a test can make the code under test behave in a way that has no counterpart in production, and it means two test files that patch the same class can interfere with each other depending on execution order. A framework that can do that needs discipline around it, and the repository's own topics list flags monkey-patching as a headline feature, which tells you the author wants it used.

So the decision is not about which framework is better. It is about whether your team wants a specification-style document as the primary artefact of testing, with the stubbing machinery included, and is prepared to set rules about which files may be monkey patched, or whether you want the mainstream PHPUnit path with mocking as a separate concern. The PHP 7.4 requirement is worth checking too, since it rules out older production runtimes.

## Conclusion

Kahlan fits a PHP team that wants RSpec-style specifications with a stubbing system built into the framework, and that would rather monkey patch a class in a test than restructure production code to make it testable. It does not fit a team committed to a PHPUnit-style suite, since adopting it means rewriting existing specs into describe-it blocks, and the monkey patching feature is powerful enough to be a hazard in a shared codebase. Before adopting it, run the bin/kahlan binary on a scratch project, check that a coverage driver is available if you need metrics, and agree in advance which files are allowed to be monkey patched, because the framework will not stop you.

## FAQ

### How do I install Kahlan?

As a development dependency with composer require --dev kahlan/kahlan, or by cloning the repository, running composer install and invoking bin/kahlan to run the specs. The runner takes a --coverage flag, for example bin/kahlan --coverage=4, which needs xdebug or phpdbg.

### What are Kahlan's requirements?

PHP 7.4 or later, Composer, and phpdbg or Xdebug, where the latter two are only required for code coverage analysis. The README stresses that the stubbing and monkey patching work without any required PECL extension.

### How does Kahlan stub functions and classes?

The allow verb names a target, toBeCalled declares that it is called and andReturn fixes the returned value, as in allow('time')->toBeCalled()->andReturn(123). For classes, toReceive takes method names, as in allow('PDO')->toReceive('prepare', 'fetchAll')->andReturn(...), and toReceive with a with clause sets an argument expectation.

### Does Kahlan need Mockery or Prophecy?

No. The README lists a handy stubbing system and states that mockery and prophecy are no longer needed. It also provides the ability to set stubs on your class methods directly, which it describes as allowing dynamic mocking.

### What reporting and export formats does Kahlan support?

There are built-in reporters, terminal or HTML reporting through istanbul or lcov, and built-in exporters for Coveralls, Code Climate, Scrutinizer and Clover. The repository also includes kahlan-config-github-action.php for running the suite in a GitHub Actions workflow.

## Sources

- [kahlan/kahlan on GitHub](https://github.com/kahlan/kahlan)
- [License: MIT](https://github.com/kahlan/kahlan/blob/dev/LICENSE)
- [Project website](https://kahlan.github.io/docs/)
- [README](https://github.com/kahlan/kahlan/blob/dev/README.md)
- [Releases](https://github.com/kahlan/kahlan/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kahlan-kahlan
