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:
The tests are designed to be "smart" — they inspect your CrudController's configuration (fields, columns, validation rules) to generate relevant assertions.
In your Laravel + Backpack project, run:
php artisan backpack:require:test-generators
It will ask you for your token & password - which you get after you purchase this package. You can later see your token and password in your Backpack account.
If the quick installation above doesn't work, here are the steps you can follow:
Step 1. Buy access to this package and you'll get an access token. Then:
auth.json file by running composer config http-basic.backpackforlaravel.com [your-token-username] [your-token-password]composer.json to look for packages in our private repo too: "repositories": [
{
"type": "composer",
"url": "https://repo.backpackforlaravel.com/"
}
],
Step 2. Install the package using Composer:
composer require --dev backpack/test-generators
Step 1. Generate feature tests for your CRUD controllers using the artisan command:
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, 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):
# 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.
| 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 |
Generate tests for all controllers:
php artisan backpack:tests
Generate tests for a specific controller:
php artisan backpack:tests --controller=UserCrudController
Generate only list operation tests:
php artisan backpack:tests --operation=list
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.
Each controller gets a single test file that extends DefaultTestBase and uses trait(s) for each operation:
tests/Feature/Admin/
├─ SomeCrudControllerTest.php # extends DefaultTestBase, uses operation traits
├─ AnotherCrudControllerTest.php
For controllers in subfolders (e.g., PetShop), the folder structure is respected:
tests/Feature/Admin/PetShop/
├─ OwnerCrudControllerTest.php
├─ PetCrudControllerTest.php
The operation test traits implement the assert logic and expose variables you can set in your test setUp() to customise behaviour:
DefaultCreateTests exposes $createInput and $assertCreateInput.DefaultUpdateTests exposes $updateInput and $assertUpdateInput.DefaultListTests and DefaultShowTests inspect the CRUD configuration via the test helper.Usage pattern:
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.$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/PetCrudControllerTest.php):
$this->createInput = array_merge($this->model::factory()->make()->toArray(), [
'avatar' => ['url' => 'https://lorempixel.com/400/200/animals'],
]);
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:
public string $route = 'pet-shop/owner/1/pets';
public array $routeParameters = ['owner' => 1];
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.
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:
use \\Tests\\Feature\\Backpack\\DefaultUpdateTests {
test_update_page_loads_successfully as default_test_update_page_loads_successfully;
}
public function test_update_page_loads_successfully(): void
{
$this->skipIfModelDoesNotHaveFactory();
$entry = $this->model::factory()->create();
$entry->owners()->attach(1, ['role' => 'Owner']);
$response = $this->get($this->testHelper->getCrudUrl($entry->getKey().'/edit'));
$response->assertStatus(200);
}
$routeParameters and the $route property includes concrete values in the generated test class.setUp() create any related models your controller requires (attached owners, categories, etc.).$createInput / $updateInput in tests when the form requires additional structured data (files, nested arrays, relationship ids).$assertCreateInput / $assertUpdateInput to shape the expected DB assertion.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 or add new operation test stubs used by the generator by publishing them to your application.
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:
<?php
namespace Tests\Feature\Backpack;
trait DefaultCloneTests
{
/**
* Test that the list page loads and there is a clone button on page
*/
public function test_create_clone_button_is_on_page(): void
{
$response = $this->get($this->testHelper->getCrudUrl('list'));
$response->assertStatus(200);
$response->assertSee('bp-button="clone"', true);
}
}
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:
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
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:
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;
}
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:
protected function setUp(): void
{
parent::setUp();
$this->createInput = [
'name' => 'Test User',
'email' => '[email protected]',
'password' => 'Password123!',
'password_confirmation' => 'Password123!',
];
$this->assertCreateInput = [
'name' => 'Test User',
'email' => '[email protected]',
];
$this->updateInput = [
'name' => 'Updated User',
'email' => '[email protected]',
'password' => 'NewPassword123!',
'password_confirmation' => 'NewPassword123!',
];
$this->assertUpdateInput = [
'name' => 'Updated User',
'email' => '[email protected]',
];
}
To submit issues, bugs and feature requests, please see our laravel-backpack/community-forum repo on Github.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
This software is proprietary & closed-source, released under the End-User License Agreement (EULA) for Private Backpack Addons. A copy of that license is also provided inside the source code - you can read that file by using the tabs at the beginning of this page.
You don't currently have access to this package. To gain access, go ahead and purchase it. You'll get: