chqthomas / approval-tests
Approval Tests Library for PHP
Requires
- php: >=7.4
- ext-json: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.68
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2025-05-09 18:11:57 UTC
README
A PHP library for approval testing. This approach allows you to verify complex results by comparing them with approved versions, making it ideal for testing outputs that are difficult to assert traditionally (e.g., HTML, JSON, XML, or binary files).
Warning
This library is still in development. It is not recommended for production use. Many features are still missing, and the API may change.
Table of Contents
- Installation
- Basic Usage
- Specialized Verifications
- Advanced Features
- Configuration
- Scrubbers
- Maintenance
- Reporters
- Symfony Integration
- Best Practices
- Contributing
- License
Installation
Install the library via Composer:
composer require chqthomas/approval-tests
Basic Usage
Simple Test
Verify a simple string output:
use ChqThomas\ApprovalTests\Approvals; public function testSimpleString(): void { Approvals::verify("Hello World"); }
The first time this runs, it generates a .received.txt
file. Approve it by renaming it to .approved.txt
or use auto-approval (see below).
Structured Data Test
Verify complex data structures like arrays or objects:
public function testArray(): void { $data = [ 'name' => 'John Doe', 'age' => 30, 'roles' => ['admin', 'user'] ]; Approvals::verify($data); }
Specialized Verifications
The library supports specific formats with dedicated methods:
HTML
Verify HTML content with automatic formatting:
public function testHtml(): void { $html = '<div>Hello <span>World</span></div>'; Approvals::verifyHtml($html); }
JSON
Verify JSON with pretty-printing and scrubbing:
public function testJson(): void { $json = '{"name":"John","age":30}'; Approvals::verifyJson($json); // Automatically formatted }
XML
Verify XML with formatting:
public function testXml(): void { $xml = '<?xml version="1.0"?><root><user>John</user></root>'; Approvals::verifyXml($xml); }
CSV
Verify CSV content:
public function testCsv(): void { $csv = "name,age\nJohn,30\nJane,25"; Approvals::verifyCsv($csv); }
Binary Files
Verify binary content (e.g., images):
public function testBinaryFile(): void { Approvals::verifyBinaryFile('path/to/image.png', 'png'); }
Advanced Features
Tests with Data Providers
Use PHPUnit data providers for parameterized tests:
/** * @dataProvider provideTestData */ public function testWithDataProvider(array $data, string $expected): void { $result = processData($data); Approvals::verify($result); } public static function provideTestData(): array { return [ 'case1' => [['input' => 1], 'output1'], 'case2' => [['input' => 2], 'output2'], ]; }
Verify All Combinations
Test all combinations of inputs:
public function testAllCombinations(): void { $operations = ['+', '-', '*', '/']; $numbers = [1, 2, 3]; Approvals::verifyAllCombinations( function($op, $a, $b) { switch($op) { case '+': return $a + $b; case '-': return $a - $b; case '*': return $a * $b; case '/': return $b != 0 ? $a / $b : 'Division by zero'; } }, [$operations, $numbers, $numbers] ); }
Configuration
Customize the library’s behavior via the Configuration
class:
PHPUnit Bootstrap Configuration
Create a tests/bootstrap.php
file to configure the library globally for all your tests:
<?php require_once __DIR__ . '/../vendor/autoload.php'; use ChqThomas\ApprovalTests\Configuration; use ChqThomas\ApprovalTests\Reporter\DiffReporter; use ChqThomas\ApprovalTests\Formatter\SymfonyObjectFormatter; // Global configuration Configuration::getInstance() ->setReporter(new DiffReporter()) ->setObjectFormatter(new SymfonyObjectFormatter()) ->setAutoApprove(false); // Configure default scrubbers for specific formats Configuration::getInstance() ->setDefaultScrubber('json', JsonScrubber::create() ->scrubMember('password', 'token') ->ignoreMember('sensitive_data')) ->setDefaultScrubber('xml', XmlScrubber::create() ->addScrubber(RegexScrubber::create([ '/\d{4}-\d{2}-\d{2}/' => '[DATE]' ])));
Then reference it in your phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="tests/bootstrap.php"> <!-- ... --> </phpunit>
Set a Custom Reporter
Change how differences are reported:
use ChqThomas\ApprovalTests\Configuration; use ChqThomas\ApprovalTests\Reporter\DiffReporter; Configuration::getInstance()->setReporter(new DiffReporter());
Use a Custom Object Formatter
Switch between default and Symfony formatters:
use ChqThomas\ApprovalTests\Formatter\SymfonyObjectFormatter; Configuration::getInstance()->setObjectFormatter(new SymfonyObjectFormatter());
Note: Requires symfony/serializer
to be installed for SymfonyObjectFormatter
.
Custom Namer
Set a custom namer for file naming:
use ChqThomas\ApprovalTests\Namer\EnvironmentAwareNamer; Configuration::getInstance()->setNamerClass(EnvironmentAwareNamer::class);
Auto-Approve Snapshots
Automatically approve new or changed snapshots:
Configuration::getInstance()->setAutoApprove(true);
Or use an environment variable:
APPROVE_SNAPSHOTS=true vendor/bin/phpunit
Scrubbers
Scrubbers normalize content before comparison, handling dynamic data like dates or IDs.
JSON Scrubbing
Scrub sensitive or variable data:
public function testJsonScrubbing(): void { $json = <<<JSON { "user": "John", "password": "secret123", "timestamp": "2024-01-01T12:00:00", "id": "550e8400-e29b-41d4-a716-446655440000" } JSON; // Default scrubbers automatically handle: // - GUIDs (replaced with Guid_1, Guid_2, etc.) // - Dates (replaced with DateTimeOffset_1, etc.) Approvals::verifyJson($json); }
Ignore JSON Members
Remove specific members:
public function testJsonIgnoreMember(): void { $json = <<<JSON { "user": "John", "sensitive": { "password": "secret123", "token": "abc123" } } JSON; Approvals::verifyJson($json, JsonScrubber::create() ->ignoreMember('sensitive')); // Member will be removed }
Scrub JSON Members
Replace members with a placeholder:
public function testJsonScrubMember(): void { $json = <<<JSON { "user": "John", "password": "secret123", "api_key": "xyz789" } JSON; Approvals::verifyJson($json, JsonScrubber::create() ->scrubMember('password', 'api_key')); // Members will be replaced with "[scrubbed]" }
XML Scrubbing
Custom scrubbing for XML:
public function testXmlScrubbing(): void { $xml = <<<XML <?xml version="1.0"?> <user> <name>John</name> <created>2024-01-01T12:00:00</created> <id>550e8400-e29b-41d4-a716-446655440000</id> </user> XML; // Custom scrubber for XML Approvals::verifyXml($xml, XmlScrubber::create() ->addScrubber(fn($content) => preg_replace('/John/', '[NAME]', $content))); }
Regex Scrubbing
Use regular expressions for generic scrubbing:
public function testRegexScrubbing(): void { $json = <<<JSON { "nodes": [ {"id": "ABC123", "name": "Node1"}, {"id": "DEF456", "name": "Node2"}, {"id": "GHI789", "name": "Node3"} ] } JSON; Approvals::verifyJson($json, JsonScrubber::create() ->addScrubber(RegexScrubber::create(['/"id": "([A-Z]{3}\d{3})"/' => '"id": "MATCHED"']))); }
Custom Scrubber
Create a custom scrubber for any content:
use ChqThomas\ApprovalTests\Scrubber\AbstractScrubber; class MyScrubber extends AbstractScrubber { public function scrub(string $content): string { // Apply base scrubbers first (GUIDs, dates) $content = $this->scrubGuids($content); $content = $this->scrubDates($content); // Add your custom rules $content = preg_replace('/secret-\d+/', '[SECRET]', $content); // Apply additional scrubbers return $this->applyAdditionalScrubbers($content); } } // Usage public function testWithCustomScrubber(): void { $content = "ID: secret-123\nDate: 2024-01-01"; Approvals::verifyWithExtension( $content, "txt", MyScrubber::create() ->addScrubber(fn($text) => str_replace('ID:', 'Reference:', $text)) ); }
Maintenance
Cleanup Received Files
Remove redundant .received
files:
use ChqThomas\ApprovalTests\ApprovalMaintenance; ApprovalMaintenance::cleanUpReceivedFiles(__DIR__ . '/tests/approvals');
Detect Orphaned Files
Find .approved
files without associated tests:
$orphanedFiles = ApprovalMaintenance::findOrphanedApprovedFiles(__DIR__ . '/tests');
Reporters
Customize how differences are reported:
CLI Reporter
Default reporter for terminal output:
use ChqThomas\ApprovalTests\Reporter\CliReporter; Configuration::getInstance()->setReporter(new CliReporter());
Diff Reporter
Show differences using a diff format:
use ChqThomas\ApprovalTests\Reporter\DiffReporter; Configuration::getInstance()->setReporter(new DiffReporter());
Composite Reporter
Combine multiple reporters:
use ChqThomas\ApprovalTests\Reporter\CompositeReporter; $reporter = new CompositeReporter([ new CliReporter(), new DiffReporter() ]); Configuration::getInstance()->setReporter($reporter);
Symfony Integration
Use with Symfony’s DomCrawler for web testing:
use ChqThomas\ApprovalTests\Symfony\ApprovalCrawlerAssertionsTrait; class MyWebTest extends WebTestCase { use ApprovalCrawlerAssertionsTrait; public function testPageContent(): void { $client = static::createClient(); $client->request('GET', '/'); self::verifySelectorHtml('#main-content'); } }
Best Practices
- Store
.approved
files in version control. - Use scrubbers for variable data (e.g., dates, IDs).
- Regularly clean up
.received
files. - Check for orphaned
.approved
files. - Use descriptive test names for clear file naming.
Contributing
Contributions are welcome! To contribute:
- Fork the project.
- Create a feature branch.
- Submit a pull request.
License
MIT License