{"id":26494756,"url":"https://github.com/lastdragon-ru/lara-asp-testing","last_synced_at":"2026-04-05T21:36:40.142Z","repository":{"id":53917619,"uuid":"330917041","full_name":"LastDragon-ru/lara-asp-testing","owner":"LastDragon-ru","description":"This package is the part of Awesome Set of Packages for Laravel","archived":false,"fork":false,"pushed_at":"2024-05-18T07:08:40.000Z","size":727,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-05-18T08:22:34.279Z","etag":null,"topics":["lara-asp","laravel","laravel-package","phpunit","phpunit-assertions","psr-7","testing"],"latest_commit_sha":null,"homepage":"https://github.com/LastDragon-ru/lara-asp","language":"PHP","has_issues":false,"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/LastDragon-ru.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2021-01-19T08:46:24.000Z","updated_at":"2024-05-18T08:22:36.239Z","dependencies_parsed_at":"2023-10-25T08:56:23.834Z","dependency_job_id":"f986e320-b736-4acc-8490-3af4294a27c9","html_url":"https://github.com/LastDragon-ru/lara-asp-testing","commit_stats":{"total_commits":299,"total_committers":2,"mean_commits":149.5,"dds":0.3946488294314381,"last_synced_commit":"9fe96b6a34ae7baae61195a9a478d5a0f9bbbdce"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LastDragon-ru%2Flara-asp-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LastDragon-ru%2Flara-asp-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LastDragon-ru%2Flara-asp-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LastDragon-ru%2Flara-asp-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LastDragon-ru","download_url":"https://codeload.github.com/LastDragon-ru/lara-asp-testing/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244592994,"owners_count":20478002,"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","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":["lara-asp","laravel","laravel-package","phpunit","phpunit-assertions","psr-7","testing"],"created_at":"2025-03-20T10:24:34.118Z","updated_at":"2026-04-05T21:36:39.850Z","avatar_url":"https://github.com/LastDragon-ru.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# (Laravel) Testing Helpers 🐝\n\nThis package provides various useful asserts for [PHPUnit](https://phpunit.de/) and better solution for HTTP tests - testing HTTP response has never been so easy! And this not only about `TestResponse` but any PSR response 😎\n\n[include:artisan]: \u003clara-asp-documentator:requirements \"{$directory}\"\u003e\n[//]: # (start: preprocess/78cfc4c7c7c55577)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n# Requirements\n\n| Requirement  | Constraint          | Supported by |\n|--------------|---------------------|------------------|\n|  PHP  | `^8.5` |  `HEAD`   |\n|  | `^8.4` |   `HEAD ⋯ 8.0.0`   |\n|  | `^8.3` |   `10.3.0 ⋯ 5.0.0`   |\n|  | `^8.2` |   `7.2.0 ⋯ 2.0.0`   |\n|  | `^8.1` |   `6.4.2 ⋯ 2.0.0`   |\n|  | `^8.0` |   `4.6.0 ⋯ 2.0.0`   |\n|  | `^8.0.0` |   `1.1.2 ⋯ 0.12.0`   |\n|  | `\u003e=8.0.0` |   `0.11.0 ⋯ 0.4.0`   |\n|  | `\u003e=7.4.0` |   `0.3.0 ⋯ 0.1.0`   |\n|  Laravel  | `^12.0.1` |   `HEAD ⋯ 9.0.0`   |\n|  | `^11.0.8` |   `8.1.1 ⋯ 8.0.0`   |\n|  | `^11.0.0` |   `7.2.0 ⋯ 6.2.0`   |\n|  | `^10.34.0` |   `7.2.0 ⋯ 6.2.0`   |\n|  | `^10.0.0` |   `6.1.0 ⋯ 2.1.0`   |\n|  | `^9.21.0` |   `5.6.0 ⋯ 5.0.0-beta.1`   |\n|  | `^9.0.0` |   `5.0.0-beta.0 ⋯ 0.12.0`   |\n|  | `^8.22.1` |   `3.0.0 ⋯ 0.2.0`   |\n|  | `^8.0` |  `0.1.0`   |\n|  PHPUnit  | `^12.5.8` |  `HEAD`   |\n|  | `^12.0.0` |   `10.3.0 ⋯ 9.0.0`   |\n|  | `^11.3.0` |   `10.3.0 ⋯ 10.1.0`   |\n|  | `^11.2.0` |  `10.0.0`   |\n|  | `^11.1.0` |  `9.3.1`  ,  `9.3.0`   |\n|  | `^11.0.0` |   `9.2.0 ⋯ 6.2.0`   |\n|  | `^10.5.0` |   `8.1.1 ⋯ 8.0.0`   |\n|  | `^10.1.0` |   `7.2.0 ⋯ 6.0.0`   |\n\n[//]: # (end: preprocess/78cfc4c7c7c55577)\n\n[include:template]: ../../docs/Shared/InstallationDev.md ({\"data\": {\"package\": \"lara-asp-testing\"}})\n[//]: # (start: preprocess/35b8d0b2d34b8b44)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n# Installation\n\n\u003e [!NOTE]\n\u003e\n\u003e The package intended to use in dev.\n\n```shell\ncomposer require --dev lastdragon-ru/lara-asp-testing\n```\n\n[//]: # (end: preprocess/35b8d0b2d34b8b44)\n\n# Usage\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e By default, package overrides scalar comparator to make it strict! So `assertEquals(true, 1)` is `false`.\n\nIn the general case, you just need to update `tests/TestCase.php` to include most important things, but you also can include only desired features, please see related traits and extensions below.\n\n[include:example]: ./docs/Examples/TestCase.php\n[//]: # (start: preprocess/564b8c0c2927454f)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace Tests;\n\nuse Illuminate\\Contracts\\Foundation\\Application;\nuse Illuminate\\Foundation\\Testing\\TestCase as BaseTestCase;\nuse LastDragon_ru\\LaraASP\\Testing\\Assertions\\Assertions;\nuse LastDragon_ru\\LaraASP\\Testing\\Concerns\\Concerns;\nuse Override;\n\nabstract class TestCase extends BaseTestCase {\n    use Assertions;         // Added\n    use Concerns;           // Added\n    use CreatesApplication;\n\n    #[Override]\n    protected function app(): Application {\n        return $this-\u003eapp;\n    }\n}\n```\n\n[//]: # (end: preprocess/564b8c0c2927454f)\n\n# Comparators\n\n\u003e [!TIP]\n\u003e\n\u003e Should be registered before test, check/use [built-in traits](./src/Concerns).\n\n## [`DatabaseQueryComparator`](./src/Comparators/DatabaseQueryComparator.php)\n\n[include:docblock]: ./src/Comparators/DatabaseQueryComparator.php\n[//]: # (start: preprocess/e008bf0a6f53648d)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nCompares two [`Query`][code-links/f2055681d6897706].\n\nWe are performing following normalization before comparison to be more precise:\n\n* Renumber `laravel_reserved_*` (it will always start from `0` and will not contain gaps)\n* Format the query by [`doctrine/sql-formatter`](https://github.com/doctrine/sql-formatter) package\n\n[//]: # (end: preprocess/e008bf0a6f53648d)\n\n## [`EloquentModelComparator`](./src/Comparators/EloquentModelComparator.php)\n\n[include:docblock]: ./src/Comparators/EloquentModelComparator.php\n[//]: # (start: preprocess/b9eae8b36fc2d911)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nCompares two Eloquent Models.\n\nThe problem is models after creating from the factory and selecting from\nthe database may have different types for the same properties. For example,\n`factory()-\u003ecreate()` will set `key` as `int`, but `select` will set it to\n`string` and (strict) comparison will fail. This comparator normalizes\nproperties types before comparison.\n\n[//]: # (end: preprocess/b9eae8b36fc2d911)\n\n# Extensions\n\n## Laravel `TestCase`\n\n### [`WithTranslations`](./src/Utils/WithTranslations.php)\n\n[include:docblock]: ./src/Utils/WithTranslations.php\n[//]: # (start: preprocess/4c9468401db9a611)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nAllows replacing translation strings for Laravel.\n\n[//]: # (end: preprocess/4c9468401db9a611)\n\n### [`Override`](./src/Concerns/Override.php)\n\n[include:docblock]: ./src/Concerns/Override.php\n[//]: # (start: preprocess/c09d2d2405dbd5d3)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nSimilar to `\\Illuminate\\Foundation\\Testing\\Concerns\\InteractsWithContainer` but will mark test as failed if\noverride was not used while test (that helps to find unused code).\n\n[//]: # (end: preprocess/c09d2d2405dbd5d3)\n\n## Eloquent Model Factory\n\n### [`FixRecentlyCreated`](./src/Database/Eloquent/Factories/FixRecentlyCreated.php)\n\n[include:docblock]: ./src/Database/Eloquent/Factories/FixRecentlyCreated.php\n[//]: # (start: preprocess/59039405fcb32123)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nAfter creating the model will have `wasRecentlyCreated = true`, in most\ncases this is unwanted behavior, this trait fixes it.\n\n[//]: # (end: preprocess/59039405fcb32123)\n\n### [`WithoutModelEvents`](./src/Database/Eloquent/Factories/WithoutModelEvents.php)\n\n[include:docblock]: ./src/Database/Eloquent/Factories/WithoutModelEvents.php\n[//]: # (start: preprocess/2a65f210857bd0bb)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nDisable models events during make/create.\n\n[//]: # (end: preprocess/2a65f210857bd0bb)\n\n# Mixins\n\n## `\\Illuminate\\Testing\\TestResponse`\n\n| Name                                                     | Description                                       |\n|----------------------------------------------------------|---------------------------------------------------|\n| [`assertThat()`](./docs/Assertions/AssertPsrResponse.md) | Asserts that response satisfies given constraint. |\n\n# Assertions\n\n[include:document-list]: ./docs/Assertions\n[//]: # (start: preprocess/c79a463462fd8331)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n## [`assertDatabaseQueryEquals`](\u003cdocs/Assertions/AssertDatabaseQueryEquals.md\u003e)\n\nAsserts that SQL Query equals SQL Query.\n\n[Read more](\u003cdocs/Assertions/AssertDatabaseQueryEquals.md\u003e).\n\n## [`assertJsonMatchesSchema`](\u003cdocs/Assertions/AssertJsonMatchesSchema.md\u003e)\n\nAsserts that JSON matches [schema](https://json-schema.org/). Validation based on the [Opis JSON Schema](https://github.com/opis/json-schema) package.\n\n[Read more](\u003cdocs/Assertions/AssertJsonMatchesSchema.md\u003e).\n\n## [`assertPsrResponse`](\u003cdocs/Assertions/AssertPsrResponse.md\u003e)\n\nAsserts that PSR Response satisfies given constraint (we have a lot of built-in [constraints](src/Constraints/Response) and [responses](src/Responses), but, of course, you can create a custom).\n\n[Read more](\u003cdocs/Assertions/AssertPsrResponse.md\u003e).\n\n## [`assertQueryLogEquals`](\u003cdocs/Assertions/AssertQueryLogEquals.md\u003e)\n\nAsserts that `QueryLog` equals `QueryLog`.\n\n[Read more](\u003cdocs/Assertions/AssertQueryLogEquals.md\u003e).\n\n## [`assertScheduled`](\u003cdocs/Assertions/AssertScheduled.md\u003e)\n\nAsserts that Schedule contains task.\n\n[Read more](\u003cdocs/Assertions/AssertScheduled.md\u003e).\n\n## [`assertScoutQueryEquals`](\u003cdocs/Assertions/AssertScoutQueryEquals.md\u003e)\n\nAsserts that Scout Query equals Scout Query.\n\n[Read more](\u003cdocs/Assertions/AssertScoutQueryEquals.md\u003e).\n\n[//]: # (end: preprocess/c79a463462fd8331)\n\n# Laravel Response Testing\n\nWhat is wrong with the [Laravel approach](https://laravel.com/docs/http-tests)? Well, there are two big problems.\n\n## Where is the error?\n\nYou never know why your test failed and need to debug it to find the reason. Real-life example:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace App\\Http\\Controllers;\n\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse Tests\\TestCase;\n\n/**\n * @internal\n */\n#[CoversClass(IndexController::class)]\nclass IndexControllerTest extends TestCase {\n    public function testIndex() {\n        $this-\u003eget('/')\n            -\u003eassertOk()\n            -\u003eassertHeader('Content-Type', 'application/json');\n    }\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eassertOk() failed\u003c/summary\u003e\n\n```text\nTesting started at 15:46 ...\nPHPUnit 9.5.0 by Sebastian Bergmann and contributors.\n\nRandom Seed:   1610451974\n\n\nExpected status code 200 but received 500.\nFailed asserting that 200 is identical to 500.\n vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:186\n app/Http/Controllers/IndexControllerTest.php:16\n\n\n\nTime: 00:01.373, Memory: 26.00 MB\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eassertHeader() failed\u003c/summary\u003e\n\n```text\nTesting started at 17:57 ...\nPHPUnit 9.5.0 by Sebastian Bergmann and contributors.\n\nRandom Seed:   1610459878\n\n\nHeader [Content-Type] was found, but value [text/html; charset=UTF-8] does not match [application/json].\nFailed asserting that two values are equal.\nExpected :'application/json'\nActual   :'text/html; charset=UTF-8'\n\u003cClick to see difference\u003e\n\n vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:229\n app/Http/Controllers/IndexControllerTest.php:18\n\n\n\nTime: 00:01.082, Memory: 24.00 MB\n\n\nFAILURES!\nTests: 1, Assertions: 3, Failures: 1.\n\nProcess finished with exit code 1\n```\n\n\u003c/details\u003e\n\n\u003e Expected status code 200 but received 500.\n\nHmmm, 500, probably this is php error? Why? Where? 😰\n\nCompare with:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace App\\Http\\Controllers;\n\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\ContentTypes\\JsonContentType;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Response;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\Ok;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse Tests\\TestCase;\n\n/**\n * @internal\n */\n#[CoversClass(IndexController::class)]\nclass IndexControllerTest extends TestCase {\n    public function testIndex() {\n        $this-\u003eget('/')-\u003eassertThat(new Response(\n            new Ok(),\n            new JsonContentType()\n        ));\n    }\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eassertThat() failed\u003c/summary\u003e\n\n```text\nPHPUnit 9.5.0 by Sebastian Bergmann and contributors.\n\nRandom Seed:   1610461475\n\n\nFailed asserting that GuzzleHttp\\Psr7\\Response Object \u0026000000001ef973410000000013328b0b (\n    'reasonPhrase' =\u003e 'Internal Server Error'\n    'statusCode' =\u003e 500\n    'headers' =\u003e Array \u00260 (\n        'cache-control' =\u003e Array \u00261 (\n            0 =\u003e 'no-cache, private'\n        )\n        'date' =\u003e Array \u00262 (\n            0 =\u003e 'Tue, 12 Jan 2021 14:24:36 GMT'\n        )\n        'content-type' =\u003e Array \u00263 (\n            0 =\u003e 'text/html; charset=UTF-8'\n        )\n    )\n    'headerNames' =\u003e Array \u00265 (\n        'cache-control' =\u003e 'cache-control'\n        'date' =\u003e 'date'\n        'content-type' =\u003e 'content-type'\n        'set-cookie' =\u003e 'Set-Cookie'\n    )\n    'protocol' =\u003e '1.1'\n    'stream' =\u003e GuzzleHttp\\Psr7\\Stream Object \u0026000000001ef972d20000000013328b0b (\n        'stream' =\u003e resource(846) of type (stream)\n        'size' =\u003e null\n        'seekable' =\u003e true\n        'readable' =\u003e true\n        'writable' =\u003e true\n        'uri' =\u003e 'php://temp'\n        'customMetadata' =\u003e Array \u00266 ()\n    )\n) has Status Code is equal to 200.\n\n\u003c!doctype html\u003e\n\u003chtml class=\"theme-light\"\u003e\n\u003c!--\nError: Call to undefined function App\\Http\\Controllers\\dview() in file app/Http/Controllers/IndexController.php on line 7\n\n#0 vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\IndexController-\u0026gt;index()\n#1 vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller-\u0026gt;callAction()\n#2 vendor/laravel/framework/src/Illuminate/Routing/Route.php(254): Illuminate\\Routing\\ControllerDispatcher-\u0026gt;dispatch()\n#3 vendor/laravel/framework/src/Illuminate/Routing/Route.php(197): Illuminate\\Routing\\Route-\u0026gt;runController()\n#4 vendor/laravel/framework/src/Illuminate/Routing/Router.php(692): Illuminate\\Routing\\Route-\u0026gt;run()\n#5 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router-\u0026gt;Illuminate\\Routing\\{closure}()\n#6 vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(41): Illuminate\\Pipeline\\Pipeline-\u0026gt;Illuminate\\Pipeline\\{closure}()\n#7 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings-\u0026gt;handle()\n#8 vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline-\u0026gt;Illuminate\\Pipeline\\{closure}()\n...\n\n\nTime: 00:01.356, Memory: 28.00 MB\n\n\nFAILURES!\nTests: 1, Assertions: 1, Failures: 1.\n\nProcess finished with exit code 1\n```\n\n\u003c/details\u003e\n\n## Reusing the test code is problematic\n\nIn most real applications you have multiple roles (eg `guest`, `user`, `admin`), guards, and policies. Very difficult to test all of them and usually you need create many `testRouteIsNotAvailableForGuest()`, `testRouteIsAvailableForAdminOnly()`, etc with a lot of boilerplate code. Also, often you cannot reuse that (boilerplate) code and must write it again and again. That is really annoying.\n\nResolving this problem is very simple. First, we need to create classes for the required Responses (actually package already provides few most [used responses](./src/Responses/Laravel) 🙄). Let's start with a simple JSON response:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace Tests\\Responses;\n\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\ContentTypes\\JsonContentType;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Response;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\Ok;\n\nclass JsonResponse extends Response {\n    public function __construct() {\n        parent::__construct(\n            new Ok(),\n            new JsonContentType(),\n        );\n    }\n}\n```\n\nNext, lets add JSON Validation Error:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace Tests\\Responses;\n\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Json\\JsonMatchesSchema;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Json\\JsonSchemaValue;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Body;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\ContentTypes\\JsonContentType;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Response;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\UnprocessableEntity;\nuse LastDragon_ru\\PhpUnit\\Utils\\TestData;\n\nclass ValidationErrorResponse extends Response {\n    use WithTestData;\n\n    public function __construct() {\n        parent::__construct(\n            new UnprocessableEntity(),\n            new JsonContentType(),\n            new Body([\n                new JsonMatchesSchema(new JsonSchemaValue(TestData::get()-\u003econtent('schema.json'))),\n            ]),\n        );\n    }\n}\n```\n\nFinally, the test:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace App\\Http\\Controllers;\n\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse Tests\\Responses;\nuse Tests\\TestCase;\n\n/**\n * @internal\n */\n#[CoversClass(IndexController::class)]\nclass IndexControllerTest extends TestCase {\n    public function testIndex() {\n        $this-\u003egetJson('/')-\u003eassertThat(new ValidationErrorResponse());\n    }\n\n    public function testTest() {\n        $this-\u003egetJson('/test')-\u003eassertThat(new ValidationErrorResponse());\n    }\n}\n```\n\nThe same test with default assertions may look something like this:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace App\\Http\\Controllers;\n\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse Tests\\TestCase;\n\n/**\n * @internal\n */\n#[CoversClass(IndexController::class)]\nclass IndexControllerTest extends TestCase {\n    public function testIndex() {\n        $this-\u003egetJson('/')\n            -\u003eassertStatus(422)\n            -\u003eassertHeader('Content-Type', 'application/json')\n            -\u003eassertJsonStructure([\n                'message',\n                'errors',\n            ]);\n    }\n\n    public function testTest() {\n        $this-\u003egetJson('/test')\n            -\u003eassertStatus(422)\n            -\u003eassertHeader('Content-Type', 'application/json')\n            -\u003eassertJsonStructure([\n                'message',\n                'errors',\n            ]);;\n    }\n}\n```\n\nFeel the difference 😉\n\n# PSR Response Testing\n\nInternally package uses `PSR-7` so you can test any `Psr\\Http\\Message\\ResponseInterface` 🤩\n\n```php\n\u003c?php declare(strict_types = 1);\n\nuse LastDragon_ru\\LaraASP\\Testing\\Assertions\\ResponseAssertions;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\ContentTypes\\JsonContentType;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Response;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\Ok;\nuse PHPUnit\\Framework\\TestCase;\n\nclass ResponseInterfaceTest extends TestCase {\n    use ResponseAssertions;\n\n    public function testResponse() {\n        /** @var \\Psr\\Http\\Message\\ResponseInterface $response */\n        $response = null;\n\n        self::assertThatResponse($response, new Response(\n            new Ok(),\n            new JsonContentType(),\n        ));\n    }\n}\n```\n\n# Data Providers on steroids\n\nThere is another cool feature that allows us to test a lot of use cases without code duplication - the [`CompositeDataProvider`](./src/Providers/CompositeDataProvider.php). It's merging multiple provides into one in the following way:\n\n```text\nProviders:\n[\n    ['expected a', 'value a'],\n    ['expected final', 'value final'],\n]\n[\n    ['expected b', 'value b'],\n    ['expected c', 'value c'],\n]\n[\n    ['expected d', 'value d'],\n    ['expected e', 'value e'],\n]\n\nMerged:\n[\n    '0 / 0 / 0' =\u003e ['expected d', 'value a', 'value b', 'value d'],\n    '0 / 0 / 1' =\u003e ['expected e', 'value a', 'value b', 'value e'],\n    '0 / 1 / 0' =\u003e ['expected d', 'value a', 'value c', 'value d'],\n    '0 / 1 / 1' =\u003e ['expected e', 'value a', 'value c', 'value e'],\n    '1'         =\u003e ['expected final', 'value final'],\n]\n```\n\nSo we can organize our tests like this:\n\n```php\n\u003c?php declare(strict_types = 1);\n\nnamespace Tests\\Feature;\n\nuse App\\Models\\User;\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Middleware\\SubstituteBindings;\nuse Illuminate\\Support\\Facades\\Route;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\Response;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\NotFound;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\Ok;\nuse LastDragon_ru\\LaraASP\\Testing\\Constraints\\Response\\StatusCodes\\Unauthorized;\nuse LastDragon_ru\\LaraASP\\Testing\\Providers\\ArrayDataProvider;\nuse LastDragon_ru\\LaraASP\\Testing\\Providers\\CompositeDataProvider;\nuse LastDragon_ru\\LaraASP\\Testing\\Providers\\DataProvider as DataProviderContract;\nuse LastDragon_ru\\LaraASP\\Testing\\Providers\\ExpectedFinal;\nuse LastDragon_ru\\LaraASP\\Testing\\Responses\\Laravel\\Json\\ValidationErrorResponse;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;use Tests\\TestCase;\n\nclass ExampleTest extends TestCase {\n    // \u003ceditor-fold desc=\"Prepare\"\u003e\n    // =========================================================================\n    public function setUp(): void {\n        parent::setUp();\n\n        Route::get('/users/{user}', function (User $user) {\n            return $user-\u003eemail;\n        })-\u003emiddleware(['auth', SubstituteBindings::class]);\n\n        Route::post('/users/{user}', function (Request $request, User $user) {\n            $user-\u003eemail = $request-\u003evalidate([\n                'email' =\u003e 'required|email',\n            ]);\n\n            return $user-\u003eemail;\n        })-\u003emiddleware(['auth', SubstituteBindings::class]);\n    }\n    // \u003c/editor-fold\u003e\n\n    // \u003ceditor-fold desc=\"Tests\"\u003e\n    // =========================================================================\n    #[DataProvider('dataProviderGet')]\n    public function testGet(Response $expected, Closure $actingAs = null, Closure $user = null): void {\n        $user = $user ? $user()-\u003egetKey() : 0;\n\n        if ($actingAs) {\n            $this-\u003eactingAs($actingAs());\n        }\n\n        $this-\u003egetJson(\"/users/{$user}\")-\u003eassertThat($expected);\n    }\n\n    #[DataProvider('dataProviderUpdate')]\n    public function testUpdate(Response $expected, Closure $actingAs = null, Closure $user = null, array $data = []) {\n        $user = $user ? $user()-\u003egetKey() : 0;\n\n        if ($actingAs) {\n            $this-\u003eactingAs($actingAs());\n        }\n\n        $this-\u003epostJson(\"/users/{$user}\", $data)-\u003eassertThat($expected);\n    }\n\n    // \u003c/editor-fold\u003e\n\n    // \u003ceditor-fold desc=\"DataProvider\"\u003e\n    // =========================================================================\n    public static function dataProviderGet(): array {\n        return (new CompositeDataProvider(\n            self::getUserDataProvider(),\n            self::getModelDataProvider(),\n        ))-\u003egetData();\n    }\n\n    public static function dataProviderUpdate(): array {\n        return (new CompositeDataProvider(\n            self::getUserDataProvider(),\n            self::getModelDataProvider(),\n            new ArrayDataProvider([\n                'no email'      =\u003e [\n                    new ValidationErrorResponse(['email' =\u003e null]),\n                    [],\n                ],\n                'invalid email' =\u003e [\n                    new ValidationErrorResponse([\n                        'email' =\u003e 'The email must be a valid email address.',\n                    ]),\n                    [\n                        'email' =\u003e '123',\n                    ],\n                ],\n                'valid email'   =\u003e [\n                    new Ok(),\n                    [\n                        'email' =\u003e 'test@example.com',\n                    ],\n                ],\n            ])\n        ))-\u003egetData();\n    }\n    // \u003c/editor-fold\u003e\n\n    // \u003ceditor-fold desc=\"Shared\"\u003e\n    // =========================================================================\n    protected static function getUserDataProvider(): DataProviderContract {\n        return new ArrayDataProvider([\n            'guest'         =\u003e [\n                new ExpectedFinal(new Unauthorized()),\n                null,\n            ],\n            'authenticated' =\u003e [\n                new Ok(),\n                function () {\n                    return User::factory()-\u003ecreate();\n                },\n            ],\n        ]);\n    }\n\n    protected static function getModelDataProvider(): DataProviderContract {\n        return new ArrayDataProvider([\n            'user not exists' =\u003e [\n                new ExpectedFinal(new NotFound()),\n                null,\n            ],\n            'user exists'     =\u003e [\n                new Ok(),\n                function () {\n                    return User::factory()-\u003ecreate();\n                },\n            ],\n        ]);\n    }\n    // \u003c/editor-fold\u003e\n}\n```\n\nEnjoy 😸\n\n# Mocking properties (Mockery) 🧪\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e Working prototype for [How to mock protected properties? (#1142)](https://github.com/mockery/mockery/issues/1142). Please note that implementation relies on Reflection and internal Mockery methods/properties. Also, PHP supports [Property Hooks](https://www.php.net/manual/en/language.oop5.property-hooks.php) since v8.4 so it highly recommended using them instead of regular properties (when Mockery will [support it](https://github.com/mockery/mockery/issues/1438) of course).\n\n[include:docblock]: ./src/Mockery/MockProperties.php ({\"summary\": false})\n[//]: # (start: preprocess/dac69ae7f0bce03d)\n[//]: # (warning: Generated automatically. Do not edit.)\n\nLimitations/Notes:\n\n* Readonly properties should be uninitialized.\n* Private properties aren't supported.\n* Property value must be an object.\n* Property must be used while test.\n* Property can be mocked only once.\n* Objects without methods will be marked as unused.\n\n[//]: # (end: preprocess/dac69ae7f0bce03d)\n\n[include:example]: ./docs/Examples/MockProperties.php\n[//]: # (start: preprocess/00f706ff1b471d60)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n```php\n\u003c?php declare(strict_types = 1);\n\n// phpcs:disable PSR1.Files.SideEffects\n// phpcs:disable PSR1.Classes.ClassDeclaration\n\nnamespace LastDragon_ru\\LaraASP\\Testing\\Docs\\Examples\\MockProperties;\n\nuse LastDragon_ru\\LaraASP\\Testing\\Mockery\\PropertiesMock;\nuse LastDragon_ru\\LaraASP\\Testing\\Mockery\\WithProperties;\nuse Mockery;\n\nreadonly class A {\n    public function __construct(\n        protected B $b,\n    ) {\n        // empty\n    }\n\n    public function a(): void {\n        $this-\u003eb-\u003eb();\n    }\n}\n\nclass B {\n    public function b(): void {\n        echo 1;\n    }\n}\n\n$mock = Mockery::mock(A::class, new WithProperties(), PropertiesMock::class);\n$mock\n    -\u003eshouldUseProperty('b')\n    -\u003evalue(\n        Mockery::mock(B::class), // or just `new B()`.\n    );\n\n$mock-\u003ea();\n```\n\n[//]: # (end: preprocess/00f706ff1b471d60)\n\n# Upgrading\n\nPlease follow [Upgrade Guide](UPGRADE.md).\n\n[include:file]: ../../docs/Shared/Contributing.md\n[//]: # (start: preprocess/c4ba75080f5a48b7)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n# Contributing\n\nPlease use the [main repository](https://github.com/LastDragon-ru/php-packages) to [report issues](https://github.com/LastDragon-ru/php-packages/issues), send [pull requests](https://github.com/LastDragon-ru/php-packages/pulls), or [ask questions](https://github.com/LastDragon-ru/php-packages/discussions).\n\n[//]: # (end: preprocess/c4ba75080f5a48b7)\n\n[//]: # (start: code-links)\n[//]: # (warning: Generated automatically. Do not edit.)\n\n[code-links/f2055681d6897706]: src/Database/QueryLog/Query.php\n    \"\\LastDragon_ru\\LaraASP\\Testing\\Database\\QueryLog\\Query\"\n\n[//]: # (end: code-links)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flastdragon-ru%2Flara-asp-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flastdragon-ru%2Flara-asp-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flastdragon-ru%2Flara-asp-testing/lists"}