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.
- PHP
^8.1 - Phalcon - either the
ext-phalconC extension (^5) or thephalcon/phalconPHP implementation (^6). Talon detects whichever is present.
composer require --dev phalcon/talon// 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();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().
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).
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');
}
}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.
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.
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);
}
}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:'],
],
]));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.
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 bashBSD-3-Clause. See LICENSE.