ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Filament 资源页面测试指南:用 Livewire 测试套件覆盖 List / Create / Edit / View 全流程

Filament 资源页面测试指南:用 Livewire 测试套件覆盖 List / Create / Edit / View 全流程 Filament 资源页面测试指南用 Livewire 测试套件覆盖 List / Create / Edit / View 全流程【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament本篇技术指南以 Filament 框架的docs/10-testing/02-testing-resources.md文档为核心系统讲解如何为 Filament 资源Resource的四大核心页面——列表List、创建Create、编辑Edit、查看View——编写自动化测试。读者将掌握actingAs认证、assertOk/assertCanSeeTableRecords等断言、fillForm/call(create)表单驱动、关系管理器Relation Manager测试、多面板与多租户场景下的测试初始化等完整实战方案并深入理解这些测试方法在 Filament 源码packages/tables/src/Testing、packages/forms/src/Testing、packages/schemas/src/Testing中的底层实现。测试前置以用户身份认证Filament 资源页面几乎都要求登录后才能访问。因此在编写任何资源测试之前首先要确保测试用例TestCase中处于已认证状态。在 PHPUnit 的TestCase中可通过setUp()方法结合actingAs()完成认证use App\Models\User; protected function setUp(): void { parent::setUp(); $this-actingAs(User::factory()-create()); }如果使用 Pest则可以在测试文件顶部通过beforeEach()完成相同的认证逻辑use App\Models\User; beforeEach(function () { $user User::factory()-create(); actingAs($user); });说明User::factory()依赖 Laravel 的模型工厂机制。在当前仓库的测试环境中tests/database/factories/UserFactory.php 即提供了此类工厂定义你可以参考其写法为真实项目中的用户模型配置工厂。测试资源列表页List页面能否正常加载资源列表页本身是一个 Livewire 组件可以直接以组件方式测试调用assertOk()断言 HTTP 响应为 200并通过assertCanSeeTableRecords()断言记录确实渲染到了表格中use App\Filament\Resources\Users\Pages\ListUsers; use App\Models\User; it(can load the page, function () { $users User::factory()-count(5)-create(); livewire(ListUsers::class) -assertOk() -assertCanSeeTableRecords($users); });从源码看assertCanSeeTableRecords()的实现位于 packages/tables/src/Testing/TestsRecords.php它会将每条记录的主键转换成形如{组件ID}.table.records.{主键}的 DOM 标识再通过assertSeeHtml()断言这些标识出现在渲染结果中。如果传入inOrder: true参数则改用assertSeeHtmlInOrder()从而可以验证记录在表格中的排列顺序。表格搜索、排序与过滤列表页测试的主体是表格能力完整的表格测试请参见 Testing tables头部或表格内的动作测试参见 Testing actions。这里给出列表页最常见的三个场景搜索测试使用searchTable()填入关键词配合assertCanSeeTableRecords()/assertCanNotSeeTableRecords()断言搜索命中结果。其底层实现packages/tables/src/Testing/TestsColumns.php本质上是对tableSearch状态赋值后触发表格重新查询use App\Filament\Resources\Users\Pages\ListUsers; use App\Models\User; it(can search users by name or email, function () { $users User::factory()-count(5)-create(); livewire(ListUsers::class) -assertCanSeeTableRecords($users) -searchTable($users-first()-name) -assertCanSeeTableRecords($users-take(1)) -assertCanNotSeeTableRecords($users-skip(1)) -searchTable($users-last()-email) -assertCanSeeTableRecords($users-take(-1)) -assertCanNotSeeTableRecords($users-take($users-count() - 1)); });排序测试使用sortTable(列名)指定排序列可传入第二个参数desc切换降序结合assertCanSeeTableRecords(..., inOrder: true)验证记录顺序。sortTable()在 packages/tables/src/Testing/TestsColumns.php 中实现为调用组件的sortTable方法use App\Filament\Resources\Users\Pages\ListUsers; use App\Models\User; it(can sort users by name, function () { $users User::factory()-count(5)-create(); livewire(ListUsers::class) -assertCanSeeTableRecords($users) -sortTable(name) -assertCanSeeTableRecords($users-sortBy(name), inOrder: true) -sortTable(name, desc) -assertCanSeeTableRecords($users-sortByDesc(name), inOrder: true); });过滤测试使用filterTable(过滤器名, 值)应用过滤器。filterTable()的实现位于 packages/tables/src/Testing/TestsFilters.php它会先调用parseTableFilterName()解析过滤器名并根据过滤器类型做适配TernaryFilter会被包装为[value true]SelectFilter会转换为[value ...]或多选时[values [...]]布尔类过滤器则映射为[isActive true]use App\Filament\Resources\Users\Pages\ListUsers; use App\Models\User; it(can filter users by locale, function () { $users User::factory()-count(5)-create(); livewire(ListUsers::class) -assertCanSeeTableRecords($users) -filterTable(locale, $users-first()-locale) -assertCanSeeTableRecords($users-where(locale, $users-first()-locale)) -assertCanNotSeeTableRecords($users-where(locale, !, $users-first()-locale)); });表格批量动作批量动作如批量删除可以通过selectTableRecords()勾选多条记录再用callAction()触发。触发批量动作时需要借助 Filament 提供的TestAction测试辅助类源码见 packages/actions/src/Testing/TestAction.php并通过-table()-bulk()声明这是一个针对表格的批量动作use App\Filament\Resources\Users\Pages\ListUsers; use App\Models\User; use Filament\Actions\Testing\TestAction; use function Pest\Laravel\assertDatabaseMissing; it(can bulk delete users, function () { $users User::factory()-count(5)-create(); livewire(ListUsers::class) -assertCanSeeTableRecords($users) -selectTableRecords($users) -callAction(TestAction::make(DeleteBulkAction::class)-table()-bulk()) -assertNotified() -assertCanNotSeeTableRecords($users); $users-each(fn (User $user) assertDatabaseMissing($user)); });TestAction::make()接收动作名称table()与bulk()会在最终提交给callAction()的上下文中写入context.table与context.bulk标记参见 TestAction::toArray()从而让测试框架精确定位到表格批量动作。详细说明见 Testing actions。测试资源创建页Create页面能否正常加载创建页同样作为 Livewire 组件测试assertOk()断言 200 响应use App\Filament\Resources\Users\Pages\CreateUser; use App\Models\User; it(can load the page, function () { livewire(CreateUser::class) -assertOk(); });表单能否正确创建记录使用fillForm()填充表单字段后调用call(create)提交再通过assertNotified()断言成功通知、assertRedirect()断言跳转最后用 Laravel/Pest 的assertDatabaseHas()验证数据库落库结果use App\Filament\Resources\Users\Pages\CreateUser; use App\Models\User; use function Pest\Laravel\assertDatabaseHas; it(can create a user, function () { $newUserData User::factory()-make(); livewire(CreateUser::class) -fillForm([ name $newUserData-name, email $newUserData-email, ]) -call(create) -assertNotified() -assertRedirect(); assertDatabaseHas(User::class, [ name $newUserData-name, email $newUserData-email, ]); });fillForm()的实现在 packages/forms/src/Testing/TestsForms.php它支持传入点号分隔的嵌套键内部通过Arr::undot()展开会自动定位到默认测试表单若组件实现了HasActions则优先使用挂载动作的表单并且会临时禁用 schema 的 state 更新钩子、特殊处理UploadedFile文件上传字段后再调用fillFormDataForTesting。表单验证规则测试通过fillForm()填充非法数据后调用call(create)用assertHasFormErrors()断言对应字段出现指定验证错误同时assertNotNotified()断言没有成功通知、assertNoRedirect()断言未发生跳转。以下示例使用 Pest 的 dataset 机制-with([...])把多条验证规则收敛到一个测试方法中避免重复编写测试代码use App\Filament\Resources\Users\Pages\CreateUser; use App\Models\User; use Illuminate\Support\Str; it(validates the form data, function (array $data, array $errors) { $newUserData User::factory()-make(); livewire(CreateUser::class) -fillForm([ name $newUserData-name, email $newUserData-email, ...$data, ]) -call(create) -assertHasFormErrors($errors) -assertNotNotified() -assertNoRedirect(); })-with([ name is required [[name null], [name required]], name is max 255 characters [[name Str::random(256)], [name max]], email is a valid email address [[email Str::random()], [email email]], email is required [[email null], [email required]], email is max 255 characters [[email Str::random(256)], [email max]], ]);提示dataset 中errors数组的键对应表单字段名值对应 Laravel 验证规则名required、max、email。assertHasFormErrors()的底层实现见 packages/forms/src/Testing/TestsForms.php它会提取组件当前的错误状态并与预期做比对。创建页表单的完整 schema 测试方案请参见 Testing schemas页面头部或表单内动作的测试参见 Testing actions。测试资源编辑页Edit页面能否正常加载编辑页需要把待编辑记录通过record参数传给 Livewire 组件。除了assertOk()还可以用assertSchemaStateSet()断言表单字段的初始值与模型数据一致assertSchemaStateSet在 packages/schemas/src/Testing/TestsSchemas.php 中实现use App\Filament\Resources\Users\Pages\EditUser; use App\Models\User; it(can load the page, function () { $user User::factory()-create(); livewire(EditUser::class, [ record $user-id, ]) -assertOk() -assertSchemaStateSet([ name $user-name, email $user-email, ]); });表单能否正确更新记录编辑页的提交动作名为save用fillForm()覆盖新值call(save)保存assertNotified()断言通知再通过assertDatabaseHas()验证数据库中的记录已被更新use App\Filament\Resources\Users\Pages\EditUser; use App\Models\User; use function Pest\Laravel\assertDatabaseHas; it(can update a user, function () { $user User::factory()-create(); $newUserData User::factory()-make(); livewire(EditUser::class, [ record $user-id, ]) -fillForm([ name $newUserData-name, email $newUserData-email, ]) -call(save) -assertNotified(); assertDatabaseHas(User::class, [ id $user-id, name $newUserData-name, email $newUserData-email, ]); });表单验证规则测试与创建页类似编辑页也可以结合 Pest dataset 测试验证规则唯一的区别是提交动作从create换成saveuse App\Filament\Resources\Users\Pages\EditUser; use App\Models\User; use Illuminate\Support\Str; it(validates the form data, function (array $data, array $errors) { $user User::factory()-create(); $newUserData User::factory()-make(); livewire(EditUser::class, [ record $user-id, ]) -fillForm([ name $newUserData-name, email $newUserData-email, ...$data, ]) -call(save) -assertHasFormErrors($errors) -assertNotNotified(); })-with([ name is required [[name null], [name required]], name is max 255 characters [[name Str::random(256)], [name max]], email is a valid email address [[email Str::random()], [email email]], email is required [[email null], [email required]], email is max 255 characters [[email Str::random(256)], [email max]], ]);编辑页上的删除动作编辑页通常挂载DeleteAction删除按钮。可以直接用callAction(DeleteAction::class)触发删除再断言通知、跳转与数据库记录消失use App\Filament\Resources\Users\Pages\EditUser; use App\Models\User; use Filament\Actions\DeleteAction; use function Pest\Laravel\assertDatabaseMissing; it(can delete a user, function () { $user User::factory()-create(); livewire(EditUser::class, [ record $user-id, ]) -callAction(DeleteAction::class) -assertNotified() -assertRedirect(); assertDatabaseMissing($user); });测试资源查看页View查看页以 Infolist 形式展示只读数据。与编辑页类似需要传入record参数assertOk()断言页面加载成功assertSchemaStateSet()则可以断言 Infolist 条目展示的值与模型一致Infolist 与表单共用同一套 schema 状态断言机制use App\Filament\Resources\Users\Pages\ViewUser; use App\Models\User; it(can load the page, function () { $user User::factory()-create(); livewire(ViewUser::class, [ record $user-id, ]) -assertOk() -assertSchemaStateSet([ name $user-name, email $user-email, ]); });Infolist 的完整测试方案参见 Testing schemas查看页头部或 Infolist 内的动作测试参见 Testing actions。测试关系管理器Relation Manager关系管理器Relation Manager是资源编辑/查看页内的子资源表格它本身也是一个 Livewire 组件因此可以有两种测试视角。断言关系管理器被渲染在编辑页组件上使用assertSeeLivewire()断言关系管理器出现在页面中use App\Filament\Resources\Users\Pages\EditUser; use App\Filament\Resources\Users\RelationManagers\PostsRelationManager; use App\Models\User; it(can load the relation manager, function () { $user User::factory()-create(); livewire(EditUser::class, [ record $user-id, ]) -assertSeeLivewire(PostsRelationManager::class); });直接测试关系管理器组件直接测试关系管理器时必须传入两个关键参数ownerRecord当前所处资源的模型记录关系管理器所属的宿主记录pageClass当前页面的类名。然后就能像测试列表页一样使用assertOk()与assertCanSeeTableRecords()use App\Filament\Resources\Users\Pages\EditUser; use App\Filament\Resources\Users\RelationManagers\PostsRelationManager; use App\Models\Post; use App\Models\User; it(can load the relation manager, function () { $user User::factory() -has(Post::factory()-count(5)) -create(); livewire(PostsRelationManager::class, [ ownerRecord $user, pageClass EditUser::class, ]) -assertOk() -assertCanSeeTableRecords($user-posts); });搜索、排序、过滤的测试方式与资源列表页完全一致参见 Testing tables。关系管理器中的动作关系管理器表格头部的动作如CreateAction同样可以测试。注意这里依然要通过TestAction::make(CreateAction::class)-table()指明这是表格动作然后把表单数据作为第二个参数传给callAction()use App\Filament\Resources\Users\Pages\EditUser; use App\Filament\Resources\Users\RelationManagers\PostsRelationManager; use App\Models\Post; use App\Models\User; use Filament\Actions\Testing\TestAction; use function Pest\Laravel\assertDatabaseHas; it(can create a post, function () { $user User::factory()-create(); $newPostData Post::factory()-make(); livewire(PostsRelationManager::class, [ ownerRecord $user, pageClass EditUser::class, ]) -callAction(TestAction::make(CreateAction::class)-table(), [ title $newPostData-title, content $newPostData-content, ]) -assertNotified(); assertDatabaseHas(Post::class, [ title $newPostData-title, content $newPostData-content, user_id $user-id, ]); });这里assertDatabaseHas中的user_id断言非常有价值它验证了关系管理器的外键自动绑定新建记录正确归属于ownerRecord这正是关系管理器测试的核心意义所在。测试创建/编辑页的getFormActions()资源页可以通过重写getFormActions()返回自定义的表单动作。测试这类动作时需要借助schemaComponent()方法定位到表单动作所在的 schema 组件表单动作位于contentschema 中组件键名为form-actions。例如在CreateUser页面的getFormActions()中自定义了Action::make(createAndVerifyEmail)测试写法如下use App\Filament\Resources\Users\Pages\CreateUser; use App\Models\User; use Filament\Actions\Testing\TestAction; it(can create a user and verify their email address, function () { livewire(CreateUser::class) -fillForm([ name Test User, email testexample.com, ]) -callAction(TestAction::make(createAndVerifyEmail)-schemaComponent(form-actions, schema: content)); expect(User::query()-where(email, testexample.com)-first()) -hasVerifiedEmail()-toBeTrue(); });从源码看schemaComponent(string|bool|null $component true, ?string $schema null)packages/actions/src/Testing/TestAction.php会把schema与组件名拼接成content.form-actions这样的上下文键写入动作上下文TestAction::toArray()测试框架据此精确定位到表单动作并执行。测试多面板Multiple Panels场景当应用配置了多个面板Panel时测试非默认面板需要手动告知 Filament 当前测试的面板。正常情况下面板是通过请求中间件在访问时自动设置的但 Livewire 组件测试并不经过 HTTP 请求因此必须手动指定use Filament\Facades\Filament; Filament::setCurrentPanel(app); // 其中 app 是待测试面板的 IDsetCurrentPanel()定义在 packages/panels/src/FilamentManager.php。可以在setUp()中统一设置影响该测试类下的所有用例也可以在单个测试开头设置只影响当前用例。测试多租户Multi-Tenant面板在多租户面板中测试资源时除了设置当前面板还需要设置当前租户。更关键的是在设置租户之后必须调用Filament::bootCurrentPanel()这样才能应用租户作用域tenant scopes与模型事件监听器use Filament\Facades\Filament; $team Team::factory()-create(); Filament::setTenant($this-team); Filament::setCurrentPanel(admin); Filament::bootCurrentPanel();从源码看bootCurrentPanel()packages/panels/src/FilamentManager.php会调用当前面板的boot()方法完成面板级初始化并通过isCurrentPanelBooted标志保证同一面板只引导一次setTenant()同文件 第 899 行则负责设置租户上下文。多租户相关的认证与中间件机制可进一步参考 Multi-factor authentication 与 Tenancy。注意当前仓库的测试套件中已有大量覆盖这些场景的实测用例例如 tests/src/Panels/Resources/RelationManagerTest.php、tests/src/Panels/Resources/Pages/CreateRecordTest.php、tests/src/Panels/Resources/Pages/EditRecordTest.php 与 tests/src/Panels/Resources/Pages/ListRecordsTest.php它们综合运用了本文介绍的fillForm、assertCanSeeTableRecords、assertHasFormErrors、TestAction等全部核心 API是学习 Filament 资源测试的最佳参考样本。总结资源测试的通用思维模型梳理全文Filament 资源测试可以归纳为一套清晰的通用流程准备数据与认证User::factory()创建用户并actingAs()多租户场景额外设置租户与面板加载页面将资源页面作为 Livewire 组件传入livewire()列表页直接传入类名编辑/查看页额外传入[record $id]关系管理器传入ownerRecord与pageClass交互与断言表格用searchTable/sortTable/filterTable/selectTableRecords表单用fillFormcall(create | save)动作统一走callAction()配合TestAction指定表格、批量、schema 组件等上下文验证结果assertOk/assertCanSeeTableRecords/assertSchemaStateSet/assertHasFormErrors/assertNotified/assertRedirect负责页面状态assertDatabaseHas/assertDatabaseMissing负责数据库落库状态。围绕这一流程Testing tables、Testing schemas、Testing actions 分别从表格、表单 schema、动作三个维度提供了更深入的测试 API 说明可依需继续深入。【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表