{"id":24743162,"url":"https://github.com/goldquality/phpunit-snapshots","last_synced_at":"2025-08-25T03:49:17.795Z","repository":{"id":270973814,"uuid":"909329194","full_name":"goldquality/phpunit-snapshots","owner":"goldquality","description":"Lazy PHP testing","archived":false,"fork":false,"pushed_at":"2025-01-27T09:23:23.000Z","size":35,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-05T00:22:21.112Z","etag":null,"topics":["php","phpunit","snapshot","snapshot-testing","testing"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/goldquality.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-28T11:28:30.000Z","updated_at":"2025-01-27T09:23:27.000Z","dependencies_parsed_at":"2025-01-05T14:37:41.439Z","dependency_job_id":null,"html_url":"https://github.com/goldquality/phpunit-snapshots","commit_stats":null,"previous_names":["goldquality/easy-test-suite-php","goldquality/phpunit-snapshots"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/goldquality/phpunit-snapshots","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goldquality%2Fphpunit-snapshots","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goldquality%2Fphpunit-snapshots/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goldquality%2Fphpunit-snapshots/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goldquality%2Fphpunit-snapshots/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/goldquality","download_url":"https://codeload.github.com/goldquality/phpunit-snapshots/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goldquality%2Fphpunit-snapshots/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272004325,"owners_count":24856937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php","phpunit","snapshot","snapshot-testing","testing"],"created_at":"2025-01-28T01:22:05.973Z","updated_at":"2025-08-25T03:49:17.773Z","avatar_url":"https://github.com/goldquality.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PHPUnit Snapshots\n\n**Snapshot testing** is a testing technique that captures the output of a function/component/endpoint and saves it as a reference.  \n\nOn subsequent test runs, the output is compared against the saved reference to ensure consistency and catch unexpected changes.\n\n___\n## PAINLESS Testing\nTesting the results of API responses can often be a tedious and monotonous task. It requires meticulously checking for specific fields, validating values, and ensuring the structure aligns with expectations. While powerful tools like Laravel's built-in testing utilities or Symfony's equivalent approaches provide fine-grained control, they tend to involve verbose and repetitive code. \n\nHere's a classic example from Laravel's official documentation:\n```php\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\npublic function test_fluent_json(): void\n{\n    $response = $this-\u003egetJson('/users/1');\n \n    $response\n    -\u003eassertJson(fn (AssertableJson $json) =\u003e\n        $json-\u003ehas('meta')\n             -\u003ehas('users', 3)\n             -\u003ehas('users.0', fn (AssertableJson $json) =\u003e\n                $json-\u003ewhere('id', 1)\n                     -\u003ewhere('name', 'Victoria Faith')\n                     -\u003ewhere('email', fn (string $email) =\u003e str($email)-\u003eis('victoria@gmail.com'))\n                     -\u003emissing('password')\n                     -\u003eetc()\n             )\n    );\n}\n\n```\n\nHere's how you can simplify the same test:\n```php\nnamespace Tests\\Feature;\n\nuse Tests\\ApiTestCase;\n\nclass ExampleTest extends ApiTestCase\n{\n    public function test_response_ok(): void\n    {\n        $response = $this-\u003egetJson('/users/1');\n\n        // Assert the response snapshot\n        $this-\u003eassertSnapshot($response-\u003ecollect()-\u003etoArray());   // Asserts that response content matches the test_response_ok_0.json\n        // or $this-\u003eassertResponseSnapshot($response);\n    }\n}\n```\nInstead of manually asserting each field and its structure, this approach stores the response in a snapshot file (e.g., test_response_ok_0.json) the first time the test runs. Future test runs will compare the current response to the saved snapshot, ensuring consistency. If the response changes intentionally, updating the snapshot is a simple process, saving significant time and effort.\n\n## Benefits\n\n- **Speed:** Quickly create tests without manually managing output assertions.\n- **Flexibility:** Combine with another asserts, like `assertDatabaseHas`, `assertQueue`, `assertCookie` and others.\n- **Consistency:** Automatically verifies outputs against previously captured snapshots, reducing human error.\n- **Focus on Differences**: When a test fails, you see the exact difference between the current response and the expected snapshot, making debugging faster.\n- **Framework Agnostic:** Compatible with popular frameworks like Symfony, Laravel, Yii. Making it versatile for any project setup.\n\n___\n\n## Comparison with competitors\n\nphpunit-snapshots vs [spatie/phpunit-snapshot-assertions](https://github.com/spatie/phpunit-snapshot-assertions):\n- phpunit-snapshots provides a robust array of masks for dynamic values, which allows for a wide set of patterns.\n- phpunit-snapshots places a protection line (\"DELETE THIS ROW\") in newly created snapshots to prompt user review and manual editing, which encourages careful validation of the snapshot’s correctness.\n___\n\n## Installation\n\n```bash\ncomposer require --dev goldquality/phpunit-snapshots\n```\n\n## Configuration\n1. Create or add to existing BaseTestCase e.g. `ApiTestCase`\n```php\n\u003c?php\n\nnamespace Tests; // laravel\n//namespace App\\Tests; // symfony\n\nuse GoldQuality\\PHPUnitSnapshots\\SnapshotAssertTrait;\nuse Illuminate\\Testing\\TestResponse;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\n\nabstract class ApiTestCase extends TestCase # laravel\n//abstract class ApiTestCase extends KernelTestCase # symfony\n{\n    use SnapshotAssertTrait;\n\n    // Additional helper method\n    public function assertResponseSnapshot($response): void\n    {\n        $this-\u003eassertSnapshot($response-\u003ecollect()-\u003etoArray()); # laravel\n        //$this-\u003eassertSnapshot($response-\u003etoArray()); # symfony\n    }\n}\n```\n[see example implementation](/tests)\n\n## Usage\n1. **Extend TestCase**: Extend the `ApiTestCase` .\n2. **Invoke assert method**: Use the `assertSnapshot()` or `assertResponseSnapshot()` method in your test cases with the response content as an argument.\n3. **Run your tests**. On the initial test run, a snapshot file is created with the JSON response content.  \n\nExample of a generated snapshot file `test_response_ok_0.json`:\n```JSON\n⚠️DELETE THIS ROW ⚠️\n{\n  \"data\": [\n    {\n      \"id\": 1,\n      \"name\": \"Joe Doe\",\n      \"balance\": 100.00,\n      \"createdAt\": \"1970-12-30 12:07:24\"\n    },\n    {\n      \"id\": \"@integer@\", # use of masks for dynamic values\n      \"name\": \"Martin Fowler\",\n      \"balance\": \"@double@.greaterThan(10).lowerThan(50.12)\", # mask conditions\n      \"createdAt\": \"@datetime@\"\n    }\n  ]\n}\n```\n4. **Edit Snapshot**: Open the created file and remove the protective line \"DELETE THIS ROW\" at the top.\n5. **Masks (Optional)**: Add [masks](#available-masks) to manage auto-generated values. \n6. **Re-run Tests**: Verify that the response contents match your established snapshots.\n\nThis method allows developers to ensure their application's output remains consistent across updates and refactoring, enhancing test coverage and reliability with minimal effort.\n___\n## Examples\n\n**Symfony Example:**\n```php\nnamespace App\\Tests\\Controller;\n\nuse App\\Tests\\ApiTestCase;\n\nclass ExampleTest extends ApiTestCase\n{\n    public function test_response_ok(): void\n    {\n        // Simulate a logged-in user\n        $client = static::createClient();\n        $client-\u003eloginUser($this-\u003egetTestUser()); // Replace with your method to fetch a test user\n        \n        // Make a JSON request\n        $response = $client-\u003erequest('GET', '/api/v1/users', [], [], [\n            'HTTP_ACCEPT' =\u003e 'application/json',\n        ]);\n\n        // Assert the response snapshot\n        $this-\u003eassertSnapshot($response-\u003etoArray()); // Asserts that response content matches the test_response_ok_0.json\n        // or $this-\u003eassertResponseSnapshot($response);\n        \n        // Additional assertions\n        $this-\u003eassertEquals(200, $response-\u003egetStatusCode());\n    }\n}\n```\n\n**Laravel Example:**\n```php\nnamespace Tests\\Feature;\n\nuse Tests\\ApiTestCase;\n\nclass ExampleTest extends ApiTestCase\n{\n    public function test_response_ok(): void\n    {\n        // Simulate a logged-in user\n        $user = User::factory()-\u003ecreate();\n        $this-\u003eactingAs($user);\n        \n        // Make a JSON request\n        $response = $this-\u003egetJson('api/v1/users');\n\n        // Assert the response snapshot\n        $this-\u003eassertSnapshot($response-\u003ecollect()-\u003etoArray());   // Asserts that response content matches the test_response_ok_0.json\n        // or $this-\u003eassertResponseSnapshot($response);\n        \n        // Additional assertions\n        $response-\u003eassertStatus(200);\n    }\n}\n```\n\n## Available masks\n- `@string@`\n- `@integer@`\n- `@number@`\n- `@double@`\n- `@boolean@`\n- `@time@`\n- `@date@`\n- `@datetime@`\n- `@timezone@` || `@tz`\n- `@array@`\n- `@array_previous@` - match next array element using pattern from previous element\n- `@array_previous_repeat@` - match all remaining array elements using pattern from previous element\n- `@...@` - unbounded array, once used matcher will skip any further array elements\n- `@null@`\n- `@*@` || `@wildcard@`\n- `@uuid@`\n- `@ulid@`\n- `@json@`\n- `@string@||@integer@` - string OR integer\n- For more patterns, see [php-matcher available patterns](https://github.com/coduo/php-matcher#available-patterns).\n\n___\n\n## Advanced configuration\n\nIf you need more control you can extend `SnapshotHandler` and implement as you need.\n```php\nprotected function initSnapshotHandler(): SnapshotHandler \n{\n   // Custom initialization logic\n}\n```\n\n## Testing\n```bash\ncomposer test\n```\n\n___\n## FAQ\n\n### How do snapshot files work?\n\nThe first time a test using `assertSnapshot()` is run, a snapshot file (in JSON format) is created, capturing the component's or function's output. In subsequent runs, the output is compared against this file. If the outputs differ, the test will fail, alerting you to unintended changes.\n\n### My test fails due to a mismatch with the snapshot; what now?\n\nIf a test fails because the output does not match the snapshot, you should:\n1. Review the differences to determine if they are expected changes.\n2. If the changes are intended, manually edit the snapshot file or delete snapshot file and re-run the test to generate a new snapshot.\n3. If the changes are unintended, investigate and fix the underlying issue in your application.\n\n### What are masks, and how do I use them?\n\nMasks are patterns used in snapshot files to handle dynamic values (e.g., timestamps, IDs) that might change between test runs. For example, `@integer@` can be used for dynamic integer values. After the first test run, you can edit the snapshot to include these masks.\n\n### Why do I need to delete the \"DELETE THIS ROW\" line in the snapshot?\n\nThe \"DELETE THIS ROW\" line serves as a protective line to remind developers to review and edit the snapshot file after its initial creation. You should remove this line to finalize the snapshot structure.\n\n### Can I customize the naming of snapshot files?\n\nCurrently, the naming of snapshot files automatically based on your test case names.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoldquality%2Fphpunit-snapshots","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoldquality%2Fphpunit-snapshots","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoldquality%2Fphpunit-snapshots/lists"}