From 7d0998ca8cdc75948fdec507386ec64a5f1e7101 Mon Sep 17 00:00:00 2001 From: pxpm Date: Tue, 10 Feb 2026 12:31:49 +0000 Subject: [PATCH 1/6] docs for testing --- 7.x/crud-testing.md | 184 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 7.x/crud-testing.md diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md new file mode 100644 index 0000000..867ce3c --- /dev/null +++ b/7.x/crud-testing.md @@ -0,0 +1,184 @@ +# Testing + +--- + + +## About + +Testing your CRUD panels ensures that your admin interfaces work as expected and continue to function correctly as your application evolves. Backpack provides a dedicated command to generate **Feature** and **Browser** tests for your CrudControllers automatically. + +These generated tests cover standard operations like: +- **List**: Asserts the table loads and columns are visible. +- **Create**: Asserts the form loads, validates inputs, and stores entries. +- **Update**: Asserts the form loads with existing data and updates entries. +- **Delete**: Asserts entries can be deleted. +- **Show**: Asserts the details view loads. + +The tests are designed to be "smart" — they inspect your CrudController's configuration (fields, columns, validation rules) to generate relevant assertions. + + +## Generate Tests + +You can generate feature tests for your CRUD controllers using the artisan command: + +```bash +php artisan backpack:tests +``` + +This will scan your controllers directory (configurable via `backpack.testing.controllers_path`) and generate test files for all supported operations. + +### Options + +| Option | Description | +| --- | --- | +| `--controller=Name` | Only generate tests for the specific controller class name (e.g., `UserCrudController`) | +| `--operation=list` | Only generate tests for the given CRUD operation (list, create, update, etc.) | +| `--type=feature` | The type of test to generate (`feature` is currently the only supported type) | +| `--framework=phpunit` | The testing framework to use (`phpunit` or `pest`). Defaults to `phpunit` | +| `--force` | Overwrite existing test classes | + +### Examples + +Generate tests for all controllers: +```bash +php artisan backpack:tests +``` + +Generate tests for a specific controller: +```bash +php artisan backpack:tests --controller=UserCrudController +``` + +Generate only list operation tests: +```bash +php artisan backpack:tests --operation=list +``` + + + +## How to configure the generated tests + +By default, the generated tests rely on your Model Factories and standard Backpack conventions to create valid data for requests. However, complex real-world applications often require specific setup, valid data states, or mocked dependencies. + +You can customize the test behavior for each CrudController by creating a **Test Configuration** class. + +### 1. Create a Configuration Class + +Create a class that implements `Backpack\CRUD\app\Library\CrudTesting\CrudTestConfiguration`. This interface allows you to define valid/invalid inputs, mock route parameters, and perform setup actions. + +```php +namespace Tests\Config; + +use Backpack\CRUD\app\Library\CrudTesting\CrudTestConfiguration; +use Backpack\CRUD\app\Library\CrudTesting\TestConfigHelper; + +class ProductConfig implements CrudTestConfiguration +{ + public function setup() + { + // E.g., create a specific category required for products + // \App\Models\Category::factory()->create(); + } + + public function getRouteParameters() + { + // If your route is /admin/category/{category_id}/product + // return ['category_id' => 1]; + return []; + } + + public function validCreateInput($model) + { + // Return valid data for a Create Request + return $model::factory()->raw([ + 'name' => 'Valid Product Name', + 'price' => 100 + ]); + } + + public function validUpdateInput($model) + { + // Return valid data for an Update Request + return $model::factory()->raw(['price' => 200]); + } + + public function invalidInput() + { + // Return data that should trigger a validation error + return ['name' => '']; // assuming name is required + } + + public static function createTestEntry(string $model, array $attributes = []) + { + return $model::factory()->create($attributes); + } + + public static function getDatabaseAssertInput(string $model, array $data = []): array + { + // Helper to filter data for database assertion (e.g. remove password_confirmation) + return TestConfigHelper::getDatabaseAssertInput($model, $data); + } +} +``` + +### 2. Register the Configuration + +Map your CrudController to your Configuration class in `config/backpack/testing.php`. + +```php +// config/backpack/testing.php + +return [ + 'configurations' => [ + \App\Http\Controllers\Admin\ProductCrudController::class => \Tests\Config\ProductConfig::class, + ], +]; +``` + +When you run your tests, `TestConfigHelper` will automatically load this configuration and use your provided inputs instead of the defaults. + + +## Customizing Test Stubs + +You can customize the test stubs used by the generator by publishing them to your application. + +```bash +php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=stubs +``` + +This will create a `resources/views/vendor/backpack/crud/stubs/crud-testing` directory in your application root. Any changes you make to these stubs will be used when generating tests. + +### Minimal Stub Example + +Here is a minimal example of what a custom operation stub (e.g., `feature-clone.stub`) might look like: + +```php + /** + * Test that the clone operation functions correctly. + */ + public function test_can_clone_item() + { + $this->actingAs($this->user); + + $entry = $this->testConfig->createTestEntry($this->model); + + $response = $this->post($this->getCrudUrl($entry->getKey().'/clone')); + + $response->assertStatus(200); + // Add more specific assertions here + } +``` + +The stub receives method content that will be injected into the generated test class. You can use available properties like `$this->model`, `$this->user`, etc. + + +### How to add new operations for test generation + +The `backpack:tests` command generates tests based on **stubs** defined in the Backpack package. Currently, stubs are provided for standard operations: `list`, `create`, `update`, `delete`, and `show`. + +If you use custom operations (e.g., `clone`, `reorder`, or your own custom actions) and want to test them, you have a few options: + +1. **Create a Stub**: If you have published the stubs (see above), you can create a new stub file for your custom operation in `resources/views/vendor/backpack/crud/stubs/crud-testing/operations`. The file name should be `feature-{operation}.stub` (e.g., `feature-clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. + + + From 455d6ebd5437512a84a9d1188b43bdad3e336c47 Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 11 Feb 2026 15:29:00 +0000 Subject: [PATCH 2/6] wip --- 7.x/crud-testing.md | 81 ++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md index 867ce3c..890822c 100644 --- a/7.x/crud-testing.md +++ b/7.x/crud-testing.md @@ -5,7 +5,7 @@ ## About -Testing your CRUD panels ensures that your admin interfaces work as expected and continue to function correctly as your application evolves. Backpack provides a dedicated command to generate **Feature** and **Browser** tests for your CrudControllers automatically. +Testing your CRUD panels ensures that your admin interfaces work as expected and continue to function correctly as your application evolves. Backpack provides a dedicated command to generate **Feature** tests for your CrudControllers automatically. These generated tests cover standard operations like: - **List**: Asserts the table loads and columns are visible. @@ -54,6 +54,22 @@ Generate only list operation tests: php artisan backpack:tests --operation=list ``` + +## Generated File Structure + +The command generates a structured set of test files for each controller. For a controller named `UserCrudController`, the structure will be: + +``` +tests/Feature/Admin/UserCrud/ +├── UserCrudTestBase.php # Base class containing setup and configuration +├── ListTest.php # Tests for List operation +├── CreateTest.php # Tests for Create operation +├── UpdateTest.php # Tests for Update operation +└── ... +``` + +The `UserCrudTestBase` class extends `Backpack\CRUD\app\Library\CrudTesting\CrudFeatureTestCase` and handles authentication and test configuration setup. Each operation test class extends this base class. + ## How to configure the generated tests @@ -64,15 +80,14 @@ You can customize the test behavior for each CrudController by creating a **Test ### 1. Create a Configuration Class -Create a class that implements `Backpack\CRUD\app\Library\CrudTesting\CrudTestConfiguration`. This interface allows you to define valid/invalid inputs, mock route parameters, and perform setup actions. +Create a class that extends `Backpack\CRUD\app\Library\CrudTesting\TestConfigHelper`. This allows you to override only the methods you need to customize, while keeping the default behavior for others. ```php namespace Tests\Config; -use Backpack\CRUD\app\Library\CrudTesting\CrudTestConfiguration; use Backpack\CRUD\app\Library\CrudTesting\TestConfigHelper; -class ProductConfig implements CrudTestConfiguration +class ProductConfig extends TestConfigHelper { public function setup() { @@ -90,48 +105,68 @@ class ProductConfig implements CrudTestConfiguration public function validCreateInput($model) { // Return valid data for a Create Request - return $model::factory()->raw([ - 'name' => 'Valid Product Name', - 'price' => 100 - ]); + // Defaults to: return $model::factory()->raw(); + + $data = parent::validCreateInput($model); + $data['name'] = 'Valid Product Name'; + return $data; } public function validUpdateInput($model) { // Return valid data for an Update Request - return $model::factory()->raw(['price' => 200]); + // Defaults to: return $model::factory()->raw(); + + $data = parent::validUpdateInput($model); + $data['price'] = 200; + return $data; } public function invalidInput() { // Return data that should trigger a validation error + // Defaults to: return []; + return ['name' => '']; // assuming name is required } - public static function createTestEntry(string $model, array $attributes = []) - { - return $model::factory()->create($attributes); - } - public static function getDatabaseAssertInput(string $model, array $data = []): array { // Helper to filter data for database assertion (e.g. remove password_confirmation) - return TestConfigHelper::getDatabaseAssertInput($model, $data); + $data = parent::getDatabaseAssertInput($model, $data); + + // unset($data['password_confirmation']); + + return $data; } } ``` ### 2. Register the Configuration -Map your CrudController to your Configuration class in `config/backpack/testing.php`. +You can configure the test generator globally in `config/backpack/testing.php`. This file allows you to map your controllers to configuration classes, but also control generation behavior. ```php // config/backpack/testing.php return [ + // The path where your CrudControllers are located + 'controllers_path' => app_path('Http/Controllers'), + + // Toggle test generation for specific operations + 'coverage' => [ + 'operations' => [ + // 'list' => true, + // 'create' => false, // Don't generate create tests + ], + ], + + // Map your CrudControllers to TestConfiguration classes 'configurations' => [ \App\Http\Controllers\Admin\ProductCrudController::class => \Tests\Config\ProductConfig::class, ], + + // ... other advanced options ]; ``` @@ -146,11 +181,11 @@ You can customize the test stubs used by the generator by publishing them to you php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=stubs ``` -This will create a `resources/views/vendor/backpack/crud/stubs/crud-testing` directory in your application root. Any changes you make to these stubs will be used when generating tests. +This will create a `resources/views/vendor/backpack/crud/stubs/testing` directory in your application root. Any changes you make to these stubs will be used when generating tests. ### Minimal Stub Example -Here is a minimal example of what a custom operation stub (e.g., `feature-clone.stub`) might look like: +Here is a minimal example of what a custom operation stub (e.g., `clone.stub`) might look like: ```php /** @@ -158,18 +193,18 @@ Here is a minimal example of what a custom operation stub (e.g., `feature-clone. */ public function test_can_clone_item() { - $this->actingAs($this->user); + // The setUp() method in the base class authenticates the user - $entry = $this->testConfig->createTestEntry($this->model); + $entry = $this->testHelper->createEntry(); - $response = $this->post($this->getCrudUrl($entry->getKey().'/clone')); + $response = $this->post($this->testHelper->getCrudUrl($entry->getKey().'/clone')); $response->assertStatus(200); // Add more specific assertions here } ``` -The stub receives method content that will be injected into the generated test class. You can use available properties like `$this->model`, `$this->user`, etc. +The stub receives method content that will be injected into the generated test class. You can use available properties like `$this->model`, `$this->testHelper`, etc. ### How to add new operations for test generation @@ -178,7 +213,7 @@ The `backpack:tests` command generates tests based on **stubs** defined in the B If you use custom operations (e.g., `clone`, `reorder`, or your own custom actions) and want to test them, you have a few options: -1. **Create a Stub**: If you have published the stubs (see above), you can create a new stub file for your custom operation in `resources/views/vendor/backpack/crud/stubs/crud-testing/operations`. The file name should be `feature-{operation}.stub` (e.g., `feature-clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. +1. **Create a Stub**: If you have published the stubs (see above), you can create a new stub file for your custom operation in `resources/views/vendor/backpack/crud/stubs/testing/feature`. The file name should be `{operation}.stub` (e.g., `clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. From 3e0ff1c3a49c4b9f7f7231d1aedcbaff180bb981 Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 11 Feb 2026 15:33:20 +0000 Subject: [PATCH 3/6] wip --- 7.x/crud-testing.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md index 890822c..8bf2a67 100644 --- a/7.x/crud-testing.md +++ b/7.x/crud-testing.md @@ -211,9 +211,7 @@ The stub receives method content that will be injected into the generated test c The `backpack:tests` command generates tests based on **stubs** defined in the Backpack package. Currently, stubs are provided for standard operations: `list`, `create`, `update`, `delete`, and `show`. -If you use custom operations (e.g., `clone`, `reorder`, or your own custom actions) and want to test them, you have a few options: - -1. **Create a Stub**: If you have published the stubs (see above), you can create a new stub file for your custom operation in `resources/views/vendor/backpack/crud/stubs/testing/feature`. The file name should be `{operation}.stub` (e.g., `clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. +If you use custom operations (e.g., `clone`, `reorder`, or your own custom actions) and want to test them you can create a **stub file** for your custom operation in `resources/views/vendor/backpack/crud/stubs/testing/feature`. The file name should be `{operation}.stub` (e.g., `clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. From de15f8e4d9f2cf41e39d1cd6e77d7f150ba0a981 Mon Sep 17 00:00:00 2001 From: pxpm Date: Thu, 19 Feb 2026 09:12:35 +0000 Subject: [PATCH 4/6] changes --- 7.x/crud-testing.md | 195 ++++++++++++++++++-------------------------- 1 file changed, 80 insertions(+), 115 deletions(-) diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md index 8bf2a67..f353164 100644 --- a/7.x/crud-testing.md +++ b/7.x/crud-testing.md @@ -55,163 +55,128 @@ php artisan backpack:tests --operation=list ``` -## Generated File Structure +## Generated testes file structure -The command generates a structured set of test files for each controller. For a controller named `UserCrudController`, the structure will be: +Generated tests rely on a small hierarchy of base classes, reusable traits and on per-controller test bases inside your app's `tests/Feature` folder. -``` -tests/Feature/Admin/UserCrud/ -├── UserCrudTestBase.php # Base class containing setup and configuration -├── ListTest.php # Tests for List operation -├── CreateTest.php # Tests for Create operation -├── UpdateTest.php # Tests for Update operation -└── ... +You will notice that there will be a new "Backpack" folder. That folder contain the "base" tests that each of your crud controllers will re-use. eg: + +```markdown +tests/Feature/Admin/SomeController/ +├─ TestBase.php # extends Tests\\Feature\\Backpack\\DefaultTestBase and sets properties +├─ CreateTest.php # uses DefaultCreateTests trait +├─ UpdateTest.php # uses DefaultUpdateTests trait +├─ ListTest.php # uses DefaultListTests trait +├─ ShowTest.php # uses DefaultShowTests trait ``` -The `UserCrudTestBase` class extends `Backpack\CRUD\app\Library\CrudTesting\CrudFeatureTestCase` and handles authentication and test configuration setup. Each operation test class extends this base class. + +## Operation test traits and configuration variables +The operation test traits implement the assert logic and expose variables you can set in your test `setUp()` to customise behaviour: - -## How to configure the generated tests +- `DefaultCreateTests` exposes `$createInput` and `$assertCreateInput`. +- `DefaultUpdateTests` exposes `$updateInput` and `$assertUpdateInput`. +- `DefaultListTests` and `DefaultShowTests` inspect the CRUD configuration via the test helper. -By default, the generated tests rely on your Model Factories and standard Backpack conventions to create valid data for requests. However, complex real-world applications often require specific setup, valid data states, or mocked dependencies. +Usage pattern: -You can customize the test behavior for each CrudController by creating a **Test Configuration** class. +- In your operation test's `setUp()` (or the controller `TestBase::setUp()`), create and set `$this->createInput` or `$this->updateInput` when you need to submit additional or transformed data. The trait will use those arrays when performing the POST/PUT requests. +- Use `$assertCreateInput` / `$assertUpdateInput` when the database assertion differs from the raw submission (for example, do not include `password` or file upload metadata in assertions). -### 1. Create a Configuration Class +Example (from `tests/Feature/Admin/PetShop/PetCrud/CreateTest.php`): -Create a class that extends `Backpack\CRUD\app\Library\CrudTesting\TestConfigHelper`. This allows you to override only the methods you need to customize, while keeping the default behavior for others. +- set an avatar URL to be submitted with the create request: ```php -namespace Tests\Config; +$this->createInput = array_merge($this->model::factory()->make()->toArray(), [ + 'avatar' => ['url' => 'https://lorempixel.com/400/200/animals'], +]); +``` + +## Route parameters and controller initialization -use Backpack\CRUD\app\Library\CrudTesting\TestConfigHelper; +Controllers that require route parameters for their routes (for example nested resources like an owner ID) can expose defaults for tests using the `TestingRouteParameters` attribute. Example found in the demo application controller: -class ProductConfig extends TestConfigHelper -{ - public function setup() - { - // E.g., create a specific category required for products - // \App\Models\Category::factory()->create(); - } +- [app/Http/Controllers/Admin/PetShop/OwnerPetsCrudController.php](app/Http/Controllers/Admin/PetShop/OwnerPetsCrudController.php) uses the attribute: - public function getRouteParameters() - { - // If your route is /admin/category/{category_id}/product - // return ['category_id' => 1]; - return []; - } +```php +#[\Backpack\CRUD\app\Library\CrudTesting\TestingRouteParameters(['owner' => 1])] +class OwnerPetsCrudController extends PetCrudController { ... } +``` - public function validCreateInput($model) - { - // Return valid data for a Create Request - // Defaults to: return $model::factory()->raw(); - - $data = parent::validCreateInput($model); - $data['name'] = 'Valid Product Name'; - return $data; - } +The attribute provides default route parameter values when the package's test helpers mock the current route. Tests can rely on those defaults. The generated controller `TestBase` sets `public string $route = 'pet-shop/owner/1/pets';` so trait requests properly include the parameter. - public function validUpdateInput($model) - { - // Return valid data for an Update Request - // Defaults to: return $model::factory()->raw(); - - $data = parent::validUpdateInput($model); - $data['price'] = 200; - return $data; - } - public function invalidInput() - { - // Return data that should trigger a validation error - // Defaults to: return []; - - return ['name' => '']; // assuming name is required - } - - public static function getDatabaseAssertInput(string $model, array $data = []): array - { - // Helper to filter data for database assertion (e.g. remove password_confirmation) - $data = parent::getDatabaseAssertInput($model, $data); - - // unset($data['password_confirmation']); - - return $data; - } + +## Overriding trait behaviour + +If the default trait behaviour doesn't match your controller logic (e.g., you need to attach relationships before asserting the edit page), override the trait methods inside your test class. You can keep the original trait implementation available by aliasing it when importing the trait. Example from `tests/Feature/Admin/PetShop/OwnerPetsCrud/UpdateTest.php`: + +```php +use \\Tests\\Feature\\Backpack\\DefaultUpdateTests { + test_update_page_loads_successfully as default_test_update_page_loads_successfully; } -``` -### 2. Register the Configuration +public function test_update_page_loads_successfully(): void +{ + $this->skipIfModelDoesNotHaveFactory(); -You can configure the test generator globally in `config/backpack/testing.php`. This file allows you to map your controllers to configuration classes, but also control generation behavior. + $entry = $this->model::factory()->create(); + $entry->owners()->attach(1, ['role' => 'Owner']); -```php -// config/backpack/testing.php - -return [ - // The path where your CrudControllers are located - 'controllers_path' => app_path('Http/Controllers'), - - // Toggle test generation for specific operations - 'coverage' => [ - 'operations' => [ - // 'list' => true, - // 'create' => false, // Don't generate create tests - ], - ], - - // Map your CrudControllers to TestConfiguration classes - 'configurations' => [ - \App\Http\Controllers\Admin\ProductCrudController::class => \Tests\Config\ProductConfig::class, - ], - - // ... other advanced options -]; + $response = $this->get($this->testHelper->getCrudUrl($entry->getKey().'/edit')); + $response->assertStatus(200); +} ``` -When you run your tests, `TestConfigHelper` will automatically load this configuration and use your provided inputs instead of the defaults. +### Short checklist when adapting or writing tests + +- Ensure the controller's required route parameters are provided by the `TestingRouteParameters` attribute or the generated `TestBase::$route` includes concrete values. +- In `TestBase::setUp()` create any related models your controller requires (attached owners, categories, etc.). +- Set `$createInput` / `$updateInput` in tests when the form requires additional structured data (files, nested arrays, relationship ids). +- Use `$assertCreateInput` / `$assertUpdateInput` to shape the expected DB assertion. +- Override trait methods only when you need custom assertions; alias the trait method if you still want to call the default behaviour. + + +## Publishing the Configuration - -## Customizing Test Stubs +You can publish the configuration by running `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=config`. There you will be able to change your controllers path. -You can customize the test stubs used by the generator by publishing them to your application. + +## Customizing or creating test stubs + +You can customize or add new operation test stubs used by the generator by publishing them to your application. ```bash php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=stubs ``` This will create a `resources/views/vendor/backpack/crud/stubs/testing` directory in your application root. Any changes you make to these stubs will be used when generating tests. +Here is an example of what a custom operation stub (e.g., `clone.stub`) might look like: -### Minimal Stub Example +```php +testHelper->createEntry(); - - $response = $this->post($this->testHelper->getCrudUrl($entry->getKey().'/clone')); - + $response = $this->get($this->testHelper->getCrudUrl('list')); $response->assertStatus(200); - // Add more specific assertions here + $response->assertSee('bp-button="clone"', true); } +} ``` -The stub receives method content that will be injected into the generated test class. You can use available properties like `$this->model`, `$this->testHelper`, etc. - - -### How to add new operations for test generation - -The `backpack:tests` command generates tests based on **stubs** defined in the Backpack package. Currently, stubs are provided for standard operations: `list`, `create`, `update`, `delete`, and `show`. - -If you use custom operations (e.g., `clone`, `reorder`, or your own custom actions) and want to test them you can create a **stub file** for your custom operation in `resources/views/vendor/backpack/crud/stubs/testing/feature`. The file name should be `{operation}.stub` (e.g., `clone.stub`). The generator will automatically use this stub when it encounters the custom operation in your controllers. + +## Troubleshooting +The test generation highly relies on your Model Factories. It's highly important that your Factories are up-to-date with the database/model requirements. From 9d958bfd49f4ab667430c23385f5c3c5b8ece6ef Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 2 Mar 2026 12:58:03 +0000 Subject: [PATCH 5/6] wip --- 7.x/crud-testing.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md index f353164..9ed37a8 100644 --- a/7.x/crud-testing.md +++ b/7.x/crud-testing.md @@ -57,17 +57,24 @@ php artisan backpack:tests --operation=list ## Generated testes file structure -Generated tests rely on a small hierarchy of base classes, reusable traits and on per-controller test bases inside your app's `tests/Feature` folder. +Generated tests rely on a small hierarchy of base classes, reusable traits and on per-controller test files inside your app's `tests/Feature` folder. -You will notice that there will be a new "Backpack" folder. That folder contain the "base" tests that each of your crud controllers will re-use. eg: +You will notice that there will be a new "Backpack" folder. That folder contain the "base" tests that each of your crud controllers will re-use. + +Each controller gets a single test file that extends `DefaultTestBase` and uses trait(s) for each operation: + +```markdown +tests/Feature/Admin/ +├─ SomeCrudControllerTest.php # extends DefaultTestBase, uses operation traits +├─ AnotherCrudControllerTest.php +``` + +For controllers in subfolders (e.g., `PetShop`), the folder structure is respected: ```markdown -tests/Feature/Admin/SomeController/ -├─ TestBase.php # extends Tests\\Feature\\Backpack\\DefaultTestBase and sets properties -├─ CreateTest.php # uses DefaultCreateTests trait -├─ UpdateTest.php # uses DefaultUpdateTests trait -├─ ListTest.php # uses DefaultListTests trait -├─ ShowTest.php # uses DefaultShowTests trait +tests/Feature/Admin/PetShop/ +├─ OwnerCrudControllerTest.php +├─ PetCrudControllerTest.php ``` @@ -81,10 +88,10 @@ The operation test traits implement the assert logic and expose variables you ca Usage pattern: -- In your operation test's `setUp()` (or the controller `TestBase::setUp()`), create and set `$this->createInput` or `$this->updateInput` when you need to submit additional or transformed data. The trait will use those arrays when performing the POST/PUT requests. +- In your test's `setUp()`, create and set `$this->createInput` or `$this->updateInput` when you need to submit additional or transformed data. The trait will use those arrays when performing the POST/PUT requests. - Use `$assertCreateInput` / `$assertUpdateInput` when the database assertion differs from the raw submission (for example, do not include `password` or file upload metadata in assertions). -Example (from `tests/Feature/Admin/PetShop/PetCrud/CreateTest.php`): +Example (from `tests/Feature/Admin/PetShop/PetCrudControllerTest.php`): - set an avatar URL to be submitted with the create request: @@ -96,22 +103,20 @@ $this->createInput = array_merge($this->model::factory()->make()->toArray(), [ ## Route parameters and controller initialization -Controllers that require route parameters for their routes (for example nested resources like an owner ID) can expose defaults for tests using the `TestingRouteParameters` attribute. Example found in the demo application controller: - -- [app/Http/Controllers/Admin/PetShop/OwnerPetsCrudController.php](app/Http/Controllers/Admin/PetShop/OwnerPetsCrudController.php) uses the attribute: +Controllers that require route parameters for their routes (for example nested resources like an owner ID) should define those parameters directly in the generated test class using the `$routeParameters` array and the `$route` property. Example from `tests/Feature/Admin/PetShop/OwnerPetsCrudControllerTest.php`: ```php -#[\Backpack\CRUD\app\Library\CrudTesting\TestingRouteParameters(['owner' => 1])] -class OwnerPetsCrudController extends PetCrudController { ... } +public string $route = 'pet-shop/owner/1/pets'; +public array $routeParameters = ['owner' => 1]; ``` -The attribute provides default route parameter values when the package's test helpers mock the current route. Tests can rely on those defaults. The generated controller `TestBase` sets `public string $route = 'pet-shop/owner/1/pets';` so trait requests properly include the parameter. +The `$routeParameters` array provides route parameter values when the test helpers mock the current route, while the `$route` property ensures trait requests target the correct URL with concrete values. ## Overriding trait behaviour -If the default trait behaviour doesn't match your controller logic (e.g., you need to attach relationships before asserting the edit page), override the trait methods inside your test class. You can keep the original trait implementation available by aliasing it when importing the trait. Example from `tests/Feature/Admin/PetShop/OwnerPetsCrud/UpdateTest.php`: +If the default trait behaviour doesn't match your controller logic (e.g., you need to attach relationships before asserting the edit page), override the trait methods inside your test class. You can keep the original trait implementation available by aliasing it when importing the trait. Example from `tests/Feature/Admin/PetShop/OwnerPetsCrudControllerTest.php`: ```php use \\Tests\\Feature\\Backpack\\DefaultUpdateTests { @@ -132,8 +137,8 @@ public function test_update_page_loads_successfully(): void ### Short checklist when adapting or writing tests -- Ensure the controller's required route parameters are provided by the `TestingRouteParameters` attribute or the generated `TestBase::$route` includes concrete values. -- In `TestBase::setUp()` create any related models your controller requires (attached owners, categories, etc.). +- Ensure the controller's required route parameters are provided via `$routeParameters` and the `$route` property includes concrete values in the generated test class. +- In `setUp()` create any related models your controller requires (attached owners, categories, etc.). - Set `$createInput` / `$updateInput` in tests when the form requires additional structured data (files, nested arrays, relationship ids). - Use `$assertCreateInput` / `$assertUpdateInput` to shape the expected DB assertion. - Override trait methods only when you need custom assertions; alias the trait method if you still want to call the default behaviour. From b55ddb154de4175456a6a4169119de330c3ccd8b Mon Sep 17 00:00:00 2001 From: pxpm Date: Thu, 19 Mar 2026 15:58:58 +0000 Subject: [PATCH 6/6] update testing docs --- 7.x/crud-testing.md | 136 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/7.x/crud-testing.md b/7.x/crud-testing.md index 9ed37a8..2ad6a31 100644 --- a/7.x/crud-testing.md +++ b/7.x/crud-testing.md @@ -19,7 +19,7 @@ The tests are designed to be "smart" — they inspect your CrudController's conf ## Generate Tests -You can generate feature tests for your CRUD controllers using the artisan command: +**Step 1.** Generate feature tests for your CRUD controllers using the artisan command: ```bash php artisan backpack:tests @@ -27,6 +27,26 @@ php artisan backpack:tests This will scan your controllers directory (configurable via `backpack.testing.controllers_path`) and generate test files for all supported operations. +**Step 2.** Configure `tests/Feature/Backpack/DefaultTestBase.php` to make sure the admin user that is used for testing... can actually do the things you're testing. Otherwise all your generator tests will fail (403 http status code instead of 200). This most likely means giving that admin user the correct roles/permissions. If you're using PermissionManager, that file includes some commented code for you, as example. + +**Step 3.** The generated tests for CrudControllers need Factories and Seeders for those Eloquent Models, in order to work. If you're using [our DevTools package](https://backpackforlaravel.com/products/devtools), they should already be there. Otherwise, frontier LLMs will do a reasonable job of generating Factories and Seeders, here's a prompt you can use to get you started: + +``` +In this Laravel application, not all Eloquent Models that have a CrudController have factories and seeders. Please do a full evaluation of CrudControllers, Models and Factories and make sure we have a full suite of Factories for any model that has a CrudController, so that we can build a test suite on top of them. +``` + +**Step 4.** You should then run your tests, to see if there's anything left to fix (there usually is): + +```bash +# how to run only the CRUD tests +php artisan test --filter="crud" + +# how to run tests only for a particular CRUD +php artisan test --filter="usercrud" +``` + +Some of the errors you meet are to be expected. We've tried to cover the most common errors in the Troubleshooting section below. We recommend taking a look at it, when debugging your CRUD tests. + ### Options | Option | Description | @@ -35,6 +55,7 @@ This will scan your controllers directory (configurable via `backpack.testing.co | `--operation=list` | Only generate tests for the given CRUD operation (list, create, update, etc.) | | `--type=feature` | The type of test to generate (`feature` is currently the only supported type) | | `--framework=phpunit` | The testing framework to use (`phpunit` or `pest`). Defaults to `phpunit` | +| `--path=` | Override the controllers path from config | | `--force` | Overwrite existing test classes | ### Examples @@ -54,8 +75,34 @@ Generate only list operation tests: php artisan backpack:tests --operation=list ``` + +## Test Status + +You can check which of your CrudControllers have tests generated and which operations are covered using: + +```bash +php artisan backpack:tests:status +``` + +This will display a visual overview of test coverage per controller: + +``` +──────────────────────────────────────────── +✓ MonsterCrudController List · Create · Update +✗ UserCrudController List · Create +──────────────────────────────────────────── +Total: 2 Tested: 1 Missing: 1 +``` + +### Options + +| Option | Description | +| --- | --- | +| `--controller=Name` | Show status for a specific controller | +| `--type=feature` | Type of tests to check | + -## Generated testes file structure +## Generated test file structure Generated tests rely on a small hierarchy of base classes, reusable traits and on per-controller test files inside your app's `tests/Feature` folder. @@ -182,6 +229,89 @@ trait DefaultCloneTests ## Troubleshooting -The test generation highly relies on your Model Factories. It's highly important that your Factories are up-to-date with the database/model requirements. +The test generation highly relies on your Model Factories. It's highly important that your Factories are up-to-date with the database/model requirements. That being said, here are a few of the most common failures people see in their generated tests, and how to fix them: + +### The field X is required + +If you see a failure like this: +``` + FAILED Tests\Feature\Admin\VenueCrudControllerTest > update endpoint modifies entry in database + Session has unexpected errors: +{ + "default": [ + "The city field is required." + ] +} +Failed asserting that true is false. +``` + +Most likely the factory for your model isn't proper. In this example, your factory is most likely missing a related city - which is mandatory in the Update operation. The test isn't your problem, but the inconsistency between your Factory and your CrudController. To make this test pass, you need to either +- (a) change your Venue factory to include a City; +- (b) change your CRUD to not have the City required; + +Alternatively, it's possible that your Factory creates an entry with `city_id` but the actual Backpack field uses a relationship field, and its name is `city` (not `city_id`). This mismatch between `city` and `city_id` can be fixed my overriding what input the CRUD tests use for the Create and Update operations, to have both `city` and `city_id`. In your `XCrudControllerTest`: + +```php + protected function setUp(): void + { + parent::setUp(); + + $data = Venue::factory()->raw(); + $data['city'] = $data['city_id']; + // unset($data['city_id']); // if you'd like + + $this->createInput = $data; + $this->updateInput = $data; + } +``` + +### The password field confirmation does not match. + +It's likely that you'll see a failure like this for the Create & Update tests, when you have a password confirmation field: + +``` + FAILED Tests\Feature\Admin\UserCrudControllerTest > update endpoint modifies entry in database + Session has unexpected errors: + +{ + "default": [ + "The password field confirmation does not match." + ] +} +Failed asserting that true is false. +``` + +One way to fix this would be to hard-code the for input that gets created/updated, in your `UserCrudControllerTest`: + +```php + protected function setUp(): void + { + parent::setUp(); + + $this->createInput = [ + 'name' => 'Test User', + 'email' => 'testuser@example.com', + 'password' => 'Password123!', + 'password_confirmation' => 'Password123!', + ]; + + $this->assertCreateInput = [ + 'name' => 'Test User', + 'email' => 'testuser@example.com', + ]; + + $this->updateInput = [ + 'name' => 'Updated User', + 'email' => 'updateduser@example.com', + 'password' => 'NewPassword123!', + 'password_confirmation' => 'NewPassword123!', + ]; + + $this->assertUpdateInput = [ + 'name' => 'Updated User', + 'email' => 'updateduser@example.com', + ]; + } +```