Hello World
First paragraph.
Second paragraph.
# Nette Tester - PHP Testing Framework Nette Tester is a lightweight, standalone PHP testing framework designed for simplicity, speed, and process isolation. It provides a productive testing environment for PHP applications without external dependencies, running on PHP 8.0+. Each test executes in a completely isolated PHP process, preventing interference between tests and ensuring reliable parallel execution with 8 threads by default. The framework features annotation-driven test configuration, comprehensive assertion methods, and multi-engine code coverage support (Xdebug, PCOV, PHPDBG). It's self-hosting, using itself for testing, and offers multiple test organization styles from simple assertion scripts to xUnit-style TestCase classes. Tests can be run individually as PHP scripts or orchestrated through the test runner for parallel execution with sophisticated output formatting. ## Core Assertions ### Assert::same() - Strict Identity Comparison Validates that two values are identical using strict comparison (===). Checks type, value, and object identity. ```php getMessage(); // "1.0 should be 1" } try { Assert::same(new stdClass, new stdClass); // fails: different instances } catch (Tester\AssertException $e) { echo $e->getMessage(); } // With custom description Assert::same(42, $calculatedValue, 'Calculation result mismatch'); ``` ### Assert::equal() - Loose Equality with Expectations Compares values with flexible rules: ignores object identity, array key order, and float precision differences. Supports Expect objects for complex assertions. ```php 1, 'b' => 2], ['b' => 2, 'a' => 1]); // passes: key order ignored // Object comparison - ignores instance identity $obj1 = new stdClass; $obj1->id = 5; $obj2 = new stdClass; $obj2->id = 5; Assert::equal($obj1, $obj2); // passes: same class and properties // Float precision - marginally different floats are equal Assert::equal(0.1 + 0.2, 0.3); // passes: handles floating point precision // Using Expect for complex structures $user = [ 'id' => 123, 'username' => 'john', 'email' => 'john@example.com', 'created_at' => new DateTime('2024-01-15'), 'roles' => ['user', 'editor'], ]; Assert::equal([ 'id' => Expect::type('int'), // type validation 'username' => 'john', // exact match 'email' => Expect::match('#.*@example\.com#'), // regex pattern 'created_at' => Expect::type(DateTime::class), // class validation 'roles' => Expect::type('array')->andCount(2), // chained expectations ], $user); // Match order and identity when needed Assert::equal([1, 2, 3], [3, 2, 1], matchOrder: false); // passes Assert::equal([1, 2, 3], [1, 2, 3], matchOrder: true); // passes Assert::equal([1, 2, 3], [3, 2, 1], matchOrder: true); // fails ``` ### Assert::exception() - Exception Testing Validates that a callable throws a specific exception type with optional message and code verification. ```php $id, 'name' => 'User ' . $id]; } } $repo = new UserRepository; // Basic exception type check Assert::exception( fn() => $repo->find(0), InvalidArgumentException::class ); // Check exception message (supports patterns) Assert::exception( fn() => $repo->find(-5), InvalidArgumentException::class, 'ID must be positive' ); // Pattern matching in message Assert::exception( fn() => $repo->find(-1), InvalidArgumentException::class, '%a%positive%a%' // wildcard pattern ); // Check exception code Assert::exception( fn() => $repo->find(0), InvalidArgumentException::class, 'ID must be positive', 100 ); // Capture exception for further inspection $e = Assert::exception( fn() => $repo->find(999), RuntimeException::class, 'User not found' ); // Now inspect the caught exception Assert::same('User not found', $e->getMessage()); Assert::type(RuntimeException::class, $e); ``` ### Assert::match() - Pattern Matching Matches strings against patterns with wildcards or regular expressions. Supports extensive wildcard syntax for flexible matching. ```php true); Assert::type('resource', fopen('php://memory', 'r')); Assert::type('null', null); Assert::type('scalar', 'test'); // string, int, float, or bool // Special type: list (array with sequential integer keys starting from 0) Assert::type('list', [1, 2, 3]); // passes Assert::type('list', ['a' => 1, 'b' => 2]); // fails: associative array try { Assert::type('list', [1 => 'a', 2 => 'b']); // fails: doesn't start at 0 } catch (Tester\AssertException $e) { echo $e->getMessage(); } // Class and interface validation class User { public function __construct(public string $name) {} } interface Repository {} class UserRepository implements Repository {} $user = new User('John'); Assert::type(User::class, $user); Assert::type('object', $user); $repo = new UserRepository; Assert::type(UserRepository::class, $repo); Assert::type(Repository::class, $repo); // interface check // With DateTime $date = new DateTime; Assert::type(DateTime::class, $date); Assert::type(DateTimeInterface::class, $date); // Using in complex assertions $response = ['code' => 200, 'data' => ['user' => new User('Alice')]]; Assert::type('array', $response); Assert::type('int', $response['code']); Assert::type(User::class, $response['data']['user']); ``` ### Assert::contains() - Containment Validation Validates that a string contains a substring or an array contains a value (strict comparison). ```php
Welcome
'; Assert::contains('Welcome', $html); Assert::contains('class="container"', $html); $permissions = ['read', 'write', 'delete']; Assert::contains('write', $permissions); // With custom description Assert::contains('success', $apiResponse, 'API response should indicate success'); // Negation Assert::notContains('error', $apiResponse); Assert::notContains('password', $loggedData); ``` ### Assert::error() - Error and Warning Testing Validates that a callable generates specific PHP errors, warnings, or notices. ```php trigger_error('Notice message', E_USER_NOTICE), 'E_USER_NOTICE', 'Notice message' ); // Multiple errors in sequence Assert::error( function () { trigger_error('First warning', E_USER_WARNING); trigger_error('Second notice', E_USER_NOTICE); }, [ [E_USER_WARNING, 'First warning'], [E_USER_NOTICE, 'Second notice'], ] ); // Pattern matching in error messages Assert::error( fn() => trigger_error('Error in module X', E_USER_ERROR), E_USER_ERROR, '%a%module%a%' ); // Testing actual PHP warnings Assert::error( fn() => 1 / 0, // Division by zero in PHP 8+ E_WARNING ); // Assert no errors occurred Assert::noError(function () { $x = 1 + 1; echo $x; }); // Catching exception as error alternative Assert::error( fn() => throw new Exception('Test exception'), Exception::class, 'Test exception' ); ``` ### Assert::count() - Count Validation Validates the number of elements in an array or Countable object. ```php 'John', 'age' => 30]); // Nested arrays $matrix = [ [1, 2, 3], [4, 5, 6], ]; Assert::count(2, $matrix); // 2 rows Assert::count(3, $matrix[0]); // 3 columns in first row // Countable objects class TaskList implements Countable { private array $tasks = []; public function add(string $task) { $this->tasks[] = $task; } public function count(): int { return count($this->tasks); } } $tasks = new TaskList; Assert::count(0, $tasks); $tasks->add('Task 1'); $tasks->add('Task 2'); Assert::count(2, $tasks); // With custom description Assert::count(10, $results, 'Expected exactly 10 search results'); // Practical usage in tests $users = fetchUsersFromDatabase(); Assert::count(5, $users, 'Should retrieve 5 users'); $filteredData = array_filter($data, fn($item) => $item['active']); Assert::count(3, $filteredData, 'Should have 3 active items'); ``` ## TestCase - xUnit Style Testing ### Basic TestCase Structure Organize tests using xUnit-style classes with setUp/tearDown lifecycle hooks and multiple test methods. ```php db = new PDO('sqlite::memory:'); $this->db->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)'); $this->manager = new UserManager($this->db); } protected function tearDown() { // Runs after each test method $this->db = null; $this->manager = null; } public function testCreateUser() { $userId = $this->manager->create('John', 'john@example.com'); Assert::type('int', $userId); Assert::true($userId > 0); $user = $this->manager->find($userId); Assert::same('John', $user['name']); Assert::same('john@example.com', $user['email']); } public function testUpdateUser() { $userId = $this->manager->create('Jane', 'jane@example.com'); $this->manager->update($userId, 'Jane Doe', 'jane.doe@example.com'); $user = $this->manager->find($userId); Assert::same('Jane Doe', $user['name']); Assert::same('jane.doe@example.com', $user['email']); } public function testDeleteUser() { $userId = $this->manager->create('Bob', 'bob@example.com'); Assert::notNull($this->manager->find($userId)); $this->manager->delete($userId); Assert::null($this->manager->find($userId)); } /** * @throws InvalidArgumentException */ public function testCreateUserWithInvalidEmail() { // Method throws InvalidArgumentException $this->manager->create('Test', 'invalid-email'); } } // Required: instantiate and run the test case (new UserManagerTest)->run(); ``` ### TestCase with Data Providers Use data providers to run the same test with multiple input sets, either from methods or external files. ```php validator = new Validator; } /** * @dataProvider getEmailValidationData */ public function testEmailValidation(string $email, bool $expected) { $result = $this->validator->isValidEmail($email); Assert::same($expected, $result); } public function getEmailValidationData(): array { return [ ['valid@example.com', true], ['user.name@example.co.uk', true], ['user+tag@example.com', true], ['invalid@', false], ['@example.com', false], ['invalid email@example.com', false], ['', false], ]; } /** * @dataProvider getPasswordStrengthData */ public function testPasswordStrength(string $password, int $expectedScore) { $score = $this->validator->getPasswordStrength($password); Assert::same($expectedScore, $score); } public function getPasswordStrengthData(): array { return [ 'weak' => ['12345', 1], 'medium' => ['Password1', 2], 'strong' => ['P@ssw0rd!123', 3], 'very_strong' => ['c0mPL3x!P@ssW0rD#2024', 4], ]; } } (new ValidatorTest)->run(); ``` ## HTTP Testing ### HttpAssert - HTTP Request Testing Test HTTP endpoints with fluent interface for validating status codes, headers, and response bodies. ```php expectCode(200) ->expectHeader('Content-Type', contains: 'application/json') ->expectBody(contains: '"id":123'); // POST request with authentication HttpAssert::fetch( 'https://api.example.com/users', method: 'POST', headers: [ 'Authorization' => 'Bearer token123', 'Content-Type' => 'application/json', ], body: '{"name":"John","email":"john@example.com"}' ) ->expectCode(201) ->expectHeader('Location') ->expectBody(contains: '"id"'); // Custom headers - both formats supported $response = HttpAssert::fetch( 'https://api.example.com/data', headers: [ 'X-API-Key' => 'secret123', // key-value format 'Accept: application/json', // string format 'User-Agent' => 'TestClient/1.0', ] ) ->expectCode(200); // Request with cookies HttpAssert::fetch( 'https://example.com/dashboard', cookies: [ 'session' => 'abc123xyz', 'user_pref' => 'dark_mode', ] ) ->expectCode(200) ->expectBody(contains: 'Welcome back'); // Follow redirects HttpAssert::fetch( 'https://example.com/old-url', follow: true ) ->expectCode(200); // Don't follow redirects HttpAssert::fetch( 'https://example.com/redirect', follow: false ) ->expectCode(302) ->expectHeader('Location', 'https://example.com/new-url'); // Status code validation with closure HttpAssert::fetch('https://api.example.com/health') ->expectCode(fn($code) => $code >= 200 && $code < 300) ->denyCode(fn($code) => $code >= 500); // Header validation patterns HttpAssert::fetch('https://api.example.com/data') ->expectHeader('Content-Type', 'application/json; charset=utf-8') // exact ->expectHeader('Server', contains: 'nginx') // contains ->expectHeader('X-Response-Time', matches: '%d% ms') // pattern ->denyHeader('X-Debug'); // must not exist // Body validation HttpAssert::fetch('https://api.example.com/users/1') ->expectBody(contains: '"status":"active"') ->expectBody(matches: '%A%"email":"%a%@%a%"%A%') ->denyBody(contains: 'password') ->denyBody(contains: 'secret'); // Custom body validation with closure HttpAssert::fetch('https://api.example.com/data') ->expectBody(function($body) { $data = json_decode($body, true); return isset($data['success']) && $data['success'] === true; }); // PUT request HttpAssert::fetch( 'https://api.example.com/users/123', method: 'PUT', headers: ['Content-Type' => 'application/json'], body: '{"name":"Updated Name"}' ) ->expectCode(200); // DELETE request HttpAssert::fetch( 'https://api.example.com/users/123', method: 'DELETE', headers: ['Authorization' => 'Bearer token123'] ) ->expectCode(204); // Testing error responses HttpAssert::fetch('https://api.example.com/invalid') ->expectCode(404) ->expectBody(contains: '"error":"Not Found"'); ``` ## DOM Query - HTML Testing ### DomQuery - CSS Selector Based HTML Querying Query and traverse HTML/XML documents using CSS selectors for testing web page structure and content. ```phpFirst paragraph.
Second paragraph.
| Name | Status | |
|---|---|---|
| John | john@example.com | Active |
| Jane | jane@example.com | Inactive |