phalcon / talon
Test harness and Phalcon bootstrapping for PHPUnit and beyond
Requires
- php: ^8.1
- phalcon/cli-options-parser: ^2.0
- symfony/browser-kit: ^6.4 || ^7.0
- symfony/dom-crawler: ^6.4 || ^7.0
Requires (Dev)
- ext-pdo: *
- ext-sqlite3: *
- friendsofphp/php-cs-fixer: ^3
- infection/infection: ^0.29.9
- pds/composer-script-names: ^1
- pds/skeleton: ^1
- phalcon/phalcon: ^6.0@alpha
- phpstan/phpstan: ^2
- phpunit/phpunit: ^10.5
- predis/predis: ^2 || ^3
- squizlabs/php_codesniffer: ^3 || ^4
Suggests
- ext-memcached: For the Services (Memcached) helpers
- ext-phalcon: Phalcon C extension (^5) - one of ext-phalcon or phalcon/phalcon is required
- phalcon/phalcon: Phalcon PHP implementation (^6) - alternative to the C extension
- predis/predis: For the Services (Redis) helpers
This package is auto-updated.
Last update: 2026-07-02 21:09:27 UTC
README
Test harness and Phalcon bootstrapping for PHPUnit and beyond - the part of Phalcon that catches the bugs.
Talon provides framework-neutral traits (the core), ready-to-extend PHPUnit base classes, and a one-liner bootstrap so any Phalcon project can write unit, integration, and functional tests with minimal boilerplate.
Requirements
- PHP
^8.1 - Phalcon - either the
ext-phalconC extension (^5) or thephalcon/phalconPHP implementation (^6). Talon detects whichever is present.
Install
composer require --dev phalcon/talon
Bootstrap (one-liner)
// tests/bootstrap.php require __DIR__ . '/../vendor/autoload.php'; use Phalcon\Talon\Settings; use Phalcon\Talon\Talon; Talon::boot(Settings::fromEnv());
Need setup hooks (the old loadIni / loadFolders)? Use the bootstrap runner:
use Phalcon\Talon\Bootstrap\Runner; use Phalcon\Talon\Bootstrap\Stage; use Phalcon\Talon\Settings; Runner::for(Settings::fromArray(['root' => __DIR__ . '/..'])) ->before(Stage::Environment, fn () => ini_set('memory_limit', '512M')) ->after(Stage::Directories, fn ($settings) => mkdir($settings->outputPath('screens'), 0777, true)) ->boot();
Unit tests
use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; final class CalculatorTest extends AbstractUnitTestCase { public function testInternal(): void { $this->assertSame(5, $this->callProtectedMethod(new Calculator(), 'add', 2, 3)); } }
AbstractUnitTestCase gives you callProtectedMethod(), getProtectedProperty(),
setProtectedProperty(), invokeMethod(), getNewFileName(), safeDeleteFile(),
safeDeleteDirectory(), assertFileContentsContains(), checkExtensionIsLoaded(), and
checkPhalconAvailable().
Database tests
use Phalcon\Talon\PHPUnit\AbstractDatabaseTestCase; final class UserTest extends AbstractDatabaseTestCase { public function testSeeded(): void { $this->assertInDatabase('users', ['email' => 'john.connor@skynet.dev']); } }
The driver comes from the driver env (sqlite, mysql, pgsql); credentials come from
Settings (env vars by default - see resources/.env.example).
Functional tests
The package never owns your container - hand it your configured application:
use Phalcon\Talon\PHPUnit\AbstractFunctionalTestCase; final class HomeTest extends AbstractFunctionalTestCase { protected function appFactory(): callable { return fn () => require __DIR__ . '/../app/bootstrap.php'; // returns a configured Application/Micro } public function testHome(): void { $this->dispatch('/'); $this->assertController('index'); $this->assertResponseContentContains('Welcome'); } }
Browser tests
For multi-request flows - login, forms, redirects - AbstractBrowserTestCase drives your
app in-process (no web server) through a symfony/browser-kit bridge, keeping cookies
and the session across requests:
use Phalcon\Talon\PHPUnit\AbstractBrowserTestCase; final class LoginTest extends AbstractBrowserTestCase { protected function appFactory(): callable { return fn () => require __DIR__ . '/../app/bootstrap.php'; } public function testLogin(): void { $this->visitPage('/session/login'); $this->fillField('email', 'sarah.connor@skynet.dev'); $this->fillField('password', 'password1'); $this->pressButton('Log In'); $this->assertPageContainsText('Search users'); } }
Verbs: visitPage, fillField, selectOption, clickLink, pressButton,
getCookie/setCookie; assertions: assertPageContainsText / assertPageMissingText.
Redirects are followed automatically. Needs symfony/browser-kit + symfony/dom-crawler.
Service tests (Redis / Memcached)
use Phalcon\Talon\PHPUnit\AbstractServicesTestCase; final class CacheTest extends AbstractServicesTestCase { public function testRedis(): void { $this->setRedisKey('key', 'value'); $this->assertSame('value', $this->getRedisKey('key')); } }
Service tests skip automatically when the backend is unreachable.
Mocking a Resultset (no database)
use Phalcon\Talon\Traits\ResultSetTrait; final class ReportTest extends \PHPUnit\Framework\TestCase { use ResultSetTrait; public function testReport(): void { $resultset = $this->mockResultSet([$modelA, $modelB]); $this->assertCount(2, $resultset); } }
Custom configuration
Override getSettings() in a project base class, or pass Settings::fromArray([...]) to
Talon::boot():
Talon::boot(Settings::fromArray([ 'root' => dirname(__DIR__), 'db' => [ 'mysql' => ['host' => '127.0.0.1', 'port' => 3306, 'dbname' => 'app', 'username' => 'root', 'password' => ''], 'sqlite' => ['dbname' => ':memory:'], ], ]));
Command line runner
vendor/bin/talon fronts PHPUnit per mapped suite:
vendor/bin/talon run # default suite (unit) vendor/bin/talon run mysql vendor/bin/talon run mysql pgsql vendor/bin/talon run all # every mapped suite, sequentially vendor/bin/talon suites # list mapped suites
With zero configuration, suites are discovered from phpunit*.xml files in the project
root and resources/ (phpunit.mysql.xml becomes mysql; phpunit.xml.dist becomes
unit, the default). Projects that need php ini flags or env vars declare a talon.php
at the project root:
return [ 'php' => ['extension=ext/modules/phalcon.so'], // global ini flags, optional 'suites' => [ 'unit' => ['config' => 'resources/phpunit.xml.dist'], 'mysql' => ['config' => 'resources/phpunit.mysql.xml'], 'pgsql' => ['config' => 'resources/phpunit.pgsql.xml'], 'sqlite' => ['config' => 'resources/phpunit.sqlite.xml'], ], 'default' => 'unit', ];
Per-suite keys: config (required), php (extra ini flags), env (extra env vars) and
args (default PHPUnit arguments) — suite entries merge over the global php/env.
Options are forwarded to PHPUnit starting at the first option talon does not recognize
itself, and everything after -- is always forwarded verbatim:
vendor/bin/talon run unit -- --filter FooTest --testdox
Each suite runs as its own subprocess (per-suite extensions and env vars work), a single suite's exit code is forwarded verbatim, and multiple suites exit with the maximum code after a per-suite summary.
Beyond PHPUnit
The traits are the core public API and carry no PHPUnit base-class requirement for their
non-assertion helpers, so Pest (uses(...)) and other runners can consume them too. Pest and
Codeception adapters are planned for a future release.
Contributing
Talon is developed entirely in Docker - see CONTRIBUTING.md for the full local-development guide. The short version:
cp resources/.env.example .env sed -i "s/^UID=.*/UID=$(id -u)/;s/^GID=.*/GID=$(id -g)/" .env docker compose run --rm app composer install # one-time: writes vendor to your checkout docker compose run --rm app composer test # or work inside the container: docker compose up -d && docker compose exec app bash
License
BSD-3-Clause. See LICENSE.