plugins/nette/skills/nette-tester/SKILL.md
Use this skill whenever writing, modifying, or running .phpt test files with Nette Tester. Invoke for any task involving Tester\Assert methods (Assert::same, Assert::match, Assert::exception, etc.), test bootstrap setup, vendor/bin/tester commands, or debugging failing test output (.expected/.actual files). Also use when the user needs to write tests for a Nette project and asks about test structure, the test() function, testException(), or assertion methods. This skill is specifically for Nette Tester – do not use for PHPUnit, Pest, Jest, Vitest, or other testing frameworks, and do not use for general PHP testing without Nette context.
npx skillsauth add nette/claude-code nette-testerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Nette Tester is a testing framework for PHP. Test files use the .phpt extension.
composer require nette/tester --dev
The bootstrap file should set up the Tester environment and enable helper functions:
<?php declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
Tester\Environment::setup();
Tester\Environment::setupFunctions(); // enables test(), testException(), testNoError(), setUp()
<?php declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
test('Calculator adds numbers correctly', function () {
$calc = new App\Model\Calculator;
Assert::same(5, $calc->add(2, 3));
});
test('Calculator throws on division by zero', function () {
$calc = new App\Model\Calculator;
Assert::exception(
fn() => $calc->divide(10, 0),
\DivisionByZeroError::class,
);
});
Key points:
test() function for each test casetest() should be a clear description of what is being testedtest() calls - the description parameter serves this purpose{ClassName}.phpt or {ClassName}.{feature}.phptAssert::same($expected, $actual) - strict identity (===)Assert::notSame($expected, $actual) - not strictly equalAssert::equal($expected, $actual) - loose comparison (ignores object identity, array key order, float epsilon)Assert::notEqual($expected, $actual)Assert::true($actual), Assert::false($actual), Assert::null($actual), Assert::notNull($actual)Assert::truthy($actual), Assert::falsey($actual)Assert::contains($needle, $haystack) - checks substring or array element; avoid for testing output (see warning below)Assert::notContains($needle, $haystack)Assert::hasKey($key, $array), Assert::hasNotKey($key, $array)Assert::count($count, $value)Assert::type($type, $value) - class/interface or built-in type ('string', 'int', 'list', etc.)Assert::match($pattern, $actual) - pattern matching with placeholders (see below)Assert::matchFile($file, $actual) - pattern loaded from fileAssert::exception($fn, $class, $message, $code) - asserts exception is thrownAssert::error($fn, $type, $message) - asserts PHP error/warning/deprecation is generatedAssert::noError($fn) - asserts no errors or exceptionsWarning about Assert::contains: Do not use Assert::contains() for testing generated output (HTML, text, etc.). It only checks for a substring - the test will pass even if the output contains errors or is completely broken, as long as the needle appears somewhere. Use Assert::match() or Assert::matchFile() instead, which verify the entire structure of the output.
Assert::match($pattern, $actual) compares a string against a pattern with placeholders. Assert::matchFile($file, $actual) works the same way but loads the pattern from a file.
Available placeholders:
| Pattern | Meaning |
|---------|---------|
| %a% | one or more of anything except end of line |
| %a?% | zero or more of anything except end of line |
| %A% | one or more of anything including end of line |
| %A?% | zero or more of anything including end of line |
| %s% / %s?% | one or more / zero or more whitespace (except EOL) |
| %S% / %S?% | one or more / zero or more non-whitespace |
| %c% | a single character (except end of line) |
| %d% / %d?% | one or more / zero or more digits |
| %i% | signed integer |
| %f% | floating point number |
| %h% | one or more HEX digits |
| %w% | one or more alphanumeric characters |
| %ds% | directory separator (/ or \) |
| %% | literal % character |
Important behavior:
%a% captures the shortest possible string\r\n and \n are treated as equivalent, so tests work cross-platform~ or #Assert::match('<div class="item">%a%</div>', $html);
// For larger patterns, use NOWDOC syntax
Assert::match(<<<'XX'
<html>
<body>%A%</body>
</html>
XX, $html);
// Or load the pattern from a file (supports the same placeholders)
Assert::matchFile(__DIR__ . '/expected/output.html', $actual);
When Assert::matchFile() fails, the expected and actual output are written to the test output directory as .expected and .actual files.
For simple single-call exceptions, use the concise fn() style:
Assert::exception(
fn() => Arrays::pick($arr, 'undefined'),
Nette\InvalidArgumentException::class,
"Missing item '%s%'.",
);
The Assert::exception() method:
%a%, %s%, etc.)For testing PHP errors and deprecations:
Assert::error(
fn() => $object->deprecatedMethod(),
E_USER_DEPRECATED,
'This method is deprecated',
);
If the entire test() block is to end with an exception, you can use testException():
testException('throws exception for invalid input', function () {
$mapper = new FilesystemMapper(__DIR__ . '/fixtures');
$mapper->getAsset('missing.txt');
}, AssetNotFoundException::class, "Asset file 'missing.txt' not found at path: %a%");
Use setUp() to run common initialization before each test() block:
$db = null;
setUp(function () use (&$db) {
$db = new TestDatabase;
});
test('insert works', function () use (&$db) {
$db->table('user')->insert(['name' => 'John']);
Assert::count(1, $db->table('user')->fetchAll());
});
test('each test gets fresh setup', function () use (&$db) {
// setUp() runs again before this test
Assert::type(TestDatabase::class, $db);
});
# Run all tests with output
vendor/bin/tester tests/ -s
# Run tests in specific directory
vendor/bin/tester tests/filters/ -s
# Run in parallel (8 threads)
vendor/bin/tester tests/ -j 8
# Run specific test file directly
php tests/common/Engine.phpt
# Run with code coverage (requires Xdebug or PCOV)
vendor/bin/tester tests/ --coverage coverage.html --coverage-src app/
When a test fails, Nette Tester writes the expected and actual output into an output directory next to the test files (e.g. tests/Tracy/output/). For each failing test Foo.phpt, you will find:
Foo.expected - what the test expected to seeFoo.actual - what was actually producedAlways look at these files first when investigating test failures. Comparing .expected vs .actual shows the exact difference and is much more informative than the short failure message printed by the runner.
For detailed information, use WebFetch on these URLs:
tools
CRITICAL: Read BEFORE writing or modifying any PHP file. A PostToolUse hook automatically runs nette/coding-standard (ECS) on every PHP file after each Edit or Write. The fixer removes unused `use` statements - so never add `use` statements in a separate edit before the code that references them. Always include `use` imports in the same edit as the referencing code, or add the code first then `use` statements. This skill should be used whenever creating new PHP files, editing existing PHP code, adding methods, refactoring, or fixing bugs in PHP - even for small one-line changes.
development
Install nette/coding-standard globally for PHP code style checking
tools
Invoke when fetching web pages from localhost, debugging PHP errors, or interpreting Tracy output (BlueScreen, Tracy Bar, dump). Read BEFORE running curl or Chrome to any local development PHP URL – with Tracy >= 2.12 and a detected agent, Tracy mirrors BlueScreen, Tracy Bar and dumps as markdown into the JS console for easy machine reading. For Chrome MCP, call list_console_messages() to read Tracy output. Essential when: 500 error, blank page, PHP exception, slow page, N+1 queries, or inspecting variables with dump().
tools
Provides Nette Utils helper classes. Use when working with Arrays, Strings, Image, Finder, FileSystem, Json, Validators, DateTime, Html element builder, Random, Callback, Type, or SmartObject from nette/utils. Do NOT use for Nette Schema, Nette Forms, Nette Database, Latte filters, or DI configuration.