主题
测试
Livewire 组件很容易测试。因为它们底层只是 Laravel 类,可以使用 Laravel 现有的测试工具进行测试。然而,Livewire 提供了许多额外的工具,使测试你的组件变得轻而易举。
本文档将指导你使用 Pest 作为推荐的测试框架来测试 Livewire 组件,当然如果你愿意,也可以使用 PHPUnit。
安装 Pest
Pest 是一个令人愉悦的 PHP 测试框架,专注于简洁性。它是 Livewire 4 中测试 Livewire 组件的推荐方式。
要在 Laravel 应用程序中安装 Pest,首先删除 PHPUnit(如果已安装)并引入 Pest:
shell
composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependencies接下来,在你的项目中初始化 Pest:
shell
./vendor/bin/pest --init这将在你的项目中创建一个 tests/Pest.php 配置文件。
有关更详细的安装说明,请参阅 Pest 安装文档。
为基于视图的组件配置 Pest
如果你在基于视图的组件(单文件或多文件)旁边编写测试,你需要配置 Pest 来识别这些测试文件。
首先,更新你的 tests/Pest.php 文件以包含 resources/views 目录:
php
pest()->extend(Tests\TestCase::class)
// ...
->in('Feature', '../resources/views');这告诉 Pest 对在 tests/Feature 目录和 resources/views 中任何位置找到的测试使用你的 TestCase 基类。
接下来,更新你的 phpunit.xml 文件以包含组件测试的测试套件:
xml
<testsuite name="Components">
<directory suffix=".test.php">resources/views</directory>
</testsuite>现在当你运行 ./vendor/bin/pest 时,Pest 将识别并运行位于组件旁边的测试。
创建你的第一个测试
你可以通过在 make:livewire 命令后附加 --test 标志来生成与组件一起的测试文件:
shell
php artisan make:livewire post.create --test对于多文件组件,这将在 resources/views/components/post/create.test.php 创建一个测试文件:
php
<?php
use Livewire\Livewire;
it('renders successfully', function () {
Livewire::test('post.create')
->assertStatus(200);
});对于基于类的组件,这会在 tests/Feature/Livewire/Post/CreateTest.php 创建一个 PHPUnit 测试文件。你可以将其转换为 Pest 语法或继续使用 PHPUnit——两者都能很好地与 Livewire 配合使用。
测试页面包含组件
你可以编写的最简单的 Livewire 测试是断言给定的端点包含并成功渲染了一个 Livewire 组件。
php
it('component exists on the page', function () {
$this->get('/posts/create')
->assertSeeLivewire('post.create');
});冒烟测试提供巨大价值
像这样的测试被称为"冒烟测试"——它们确保你的应用程序没有灾难性问题。虽然简单,但这些测试提供了巨大的价值,因为它们几乎不需要维护,并且给你一个基本的信心,确保你的页面成功渲染。
浏览器测试
Pest v4 包含由 Playwright 驱动的一流浏览器测试支持。这允许你在真实浏览器中测试你的 Livewire 组件,像用户一样与它们交互。
安装浏览器测试
首先,安装 Pest 浏览器插件:
shell
composer require pestphp/pest-plugin-browser --dev接下来,通过 npm 安装 Playwright:
shell
npm install playwright@latest
npx playwright install有关完整的浏览器测试文档,请参阅 Pest 浏览器测试指南。
编写浏览器测试
你可以使用 Livewire::visit() 而不是 Livewire::test() 在真实浏览器中测试你的组件:
php
it('can create a new post', function () {
Livewire::visit('post.create')
->type('[wire\:model="title"]', 'My first post')
->type('[wire\:model="content"]', 'This is the content')
->press('Save')
->assertSee('Post created successfully');
});浏览器测试比单元测试慢,但提供端到端的信心,确保你的组件在真实浏览器环境中按预期工作。
有关可用浏览器测试断言的完整列表,请参阅 Pest 浏览器测试断言。
何时使用浏览器测试
对关键用户流程和复杂交互使用浏览器测试。对于大多数组件测试,标准的 Livewire::test() 方法更快且足够。
测试视图
Livewire 提供 assertSee() 来验证文本出现在组件的渲染输出中:
php
use App\Models\Post;
it('displays posts', function () {
Post::factory()->create(['title' => 'My first post']);
Post::factory()->create(['title' => 'My second post']);
Livewire::test('show-posts')
->assertSee('My first post')
->assertSee('My second post');
});断言视图数据
有时测试传递给视图的数据而不是渲染输出会很有帮助:
php
use App\Models\Post;
it('passes all posts to the view', function () {
Post::factory()->count(3)->create();
Livewire::test('show-posts')
->assertViewHas('posts', function ($posts) {
return count($posts) === 3;
});
});对于简单的断言,你可以直接传递期望的值:
php
Livewire::test('show-posts')
->assertViewHas('postCount', 3);使用身份验证测试
大多数应用程序需要用户登录。与其在每个测试开始时手动进行身份验证,不如使用 actingAs() 方法:
php
use App\Models\User;
use App\Models\Post;
it('user only sees their own posts', function () {
$user = User::factory()
->has(Post::factory()->count(3))
->create();
$stranger = User::factory()
->has(Post::factory()->count(2))
->create();
Livewire::actingAs($user)
->test('show-posts')
->assertViewHas('posts', function ($posts) {
return count($posts) === 3;
});
});测试属性
Livewire 提供了设置和断言组件属性的工具。
使用 set() 更新属性,使用 assertSet() 验证其值:
php
it('can set the title property', function () {
Livewire::test('post.create')
->set('title', 'My amazing post')
->assertSet('title', 'My amazing post');
});初始化属性
组件通常从父组件或路由参数接收数据。将此数据作为第二个参数传递给 Livewire::test():
php
use App\Models\Post;
it('title field is populated when editing', function () {
$post = Post::factory()->create([
'title' => 'Existing post title',
]);
Livewire::test('post.edit', ['post' => $post])
->assertSet('title', 'Existing post title');
});设置 URL 参数
如果你的组件使用 Livewire 的 URL 功能 来跟踪查询字符串中的状态,使用 withQueryParams() 来模拟 URL 参数:
php
use App\Models\Post;
it('can search posts via url query string', function () {
Post::factory()->create(['title' => 'Laravel testing']);
Post::factory()->create(['title' => 'Vue components']);
Livewire::withQueryParams(['search' => 'Laravel'])
->test('search-posts')
->assertSee('Laravel testing')
->assertDontSee('Vue components');
});设置 Cookies
使用 withCookie() 或 withCookies() 为你的测试设置 cookies:
php
it('loads discount token from cookie', function () {
Livewire::withCookies(['discountToken' => 'SUMMER2024'])
->test('cart')
->assertSet('discountToken', 'SUMMER2024');
});调用操作
使用 call() 方法在测试中触发组件操作:
php
use App\Models\Post;
it('can create a post', function () {
expect(Post::count())->toBe(0);
Livewire::test('post.create')
->set('title', 'My new post')
->set('content', 'Post content here')
->call('save');
expect(Post::count())->toBe(1);
});Pest 期望
上面的示例使用 Pest 的 expect() 语法进行断言。有关可用期望的完整列表,请参阅 Pest 期望文档。
你可以向操作传递参数:
php
Livewire::test('post.show')
->call('deletePost', $postId);测试验证
使用 assertHasErrors() 断言验证错误已被抛出:
php
it('title field is required', function () {
Livewire::test('post.create')
->set('title', '')
->call('save')
->assertHasErrors('title');
});测试特定的验证规则:
php
it('title must be at least 3 characters', function () {
Livewire::test('post.create')
->set('title', 'ab')
->call('save')
->assertHasErrors(['title' => ['min:3']]);
});测试授权
使用 assertUnauthorized() 和 assertForbidden() 确保授权检查正常工作:
php
use App\Models\User;
use App\Models\Post;
it('cannot update another users post', function () {
$user = User::factory()->create();
$stranger = User::factory()->create();
$post = Post::factory()->for($stranger)->create();
Livewire::actingAs($user)
->test('post.edit', ['post' => $post])
->set('title', 'Hacked!')
->call('save')
->assertForbidden();
});测试重定向
断言操作执行了重定向:
php
it('redirects to posts index after creating', function () {
Livewire::test('post.create')
->set('title', 'New post')
->set('content', 'Content here')
->call('save')
->assertRedirect('/posts');
});你还可以断言重定向到命名路由或页面组件:
php
->assertRedirect(route('posts.index'));
->assertRedirectToRoute('posts.index');测试事件
断言从组件分发了事件:
php
it('dispatches event when post is created', function () {
Livewire::test('post.create')
->set('title', 'New post')
->call('save')
->assertDispatched('post-created');
});测试组件之间的事件通信:
php
it('updates post count when event is dispatched', function () {
$badge = Livewire::test('post-count-badge')
->assertSee('0');
Livewire::test('post.create')
->set('title', 'New post')
->call('save')
->assertDispatched('post-created');
$badge->dispatch('post-created')
->assertSee('1');
});断言事件使用特定参数分发:
php
it('dispatches notification when deleting post', function () {
Livewire::test('post.show')
->call('delete', postId: 3)
->assertDispatched('notify', message: 'Post deleted');
});对于复杂的断言,使用闭包:
php
it('dispatches event with correct data', function () {
Livewire::test('post.show')
->call('delete', postId: 3)
->assertDispatched('notify', function ($event, $params) {
return ($params['message'] ?? '') === 'Post deleted';
});
});使用 PHPUnit
虽然推荐使用 Pest,但你完全可以使用 PHPUnit 来测试 Livewire 组件。所有相同的测试工具都可以与 PHPUnit 的语法一起使用。
以下是一个 PHPUnit 示例供比较:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class CreatePostTest extends TestCase
{
public function test_can_create_post()
{
$this->assertEquals(0, Post::count());
Livewire::test('post.create')
->set('title', 'My new post')
->set('content', 'Post content')
->call('save');
$this->assertEquals(1, Post::count());
}
public function test_title_is_required()
{
Livewire::test('post.create')
->set('title', '')
->call('save')
->assertHasErrors('title');
}
}本页面记录的所有功能都与 PHPUnit 完全相同——只需使用 PHPUnit 的断言语法代替 Pest 的。
考虑尝试 Pest
如果你有兴趣探索 Pest 更优雅的语法和功能,请查看 pestphp.com 了解更多。
所有可用的测试方法
以下是所有可用 Livewire 测试方法的综合参考:
设置方法
| 方法 | 描述 |
|---|---|
Livewire::test('post.create') | 测试 post.create 组件 |
Livewire::test(UpdatePost::class, ['post' => $post]) | 使用传递给 mount() 的参数测试 UpdatePost 组件 |
Livewire::actingAs($user) | 为测试设置已认证用户 |
Livewire::withQueryParams(['search' => '...']) | 设置 URL 查询参数(例如 ?search=...) |
Livewire::withCookie('name', 'value') | 为测试设置 cookie |
Livewire::withCookies(['color' => 'blue', 'name' => 'Taylor']) | 设置多个 cookies |
Livewire::withHeaders(['X-Header' => 'value']) | 设置自定义头 |
Livewire::withoutLazyLoading() | 在此测试中禁用所有组件的懒加载 |
与组件交互
| 方法 | 描述 |
|---|---|
set('title', '...') | 将 title 属性设置为提供的值 |
set(['title' => '...', 'content' => '...']) | 使用数组设置多个属性 |
toggle('sortAsc') | 在 true 和 false 之间切换布尔属性 |
call('save') | 调用 save 操作/方法 |
call('remove', $postId) | 使用参数调用方法 |
refresh() | 触发组件重新渲染 |
dispatch('post-created') | 从组件分发事件 |
dispatch('post-created', postId: $post->id) | 使用参数分发事件 |
断言
| 方法 | 描述 |
|---|---|
assertSet('title', '...') | 断言属性等于提供的值 |
assertNotSet('title', '...') | 断言属性不等于提供的值 |
assertCount('posts', 3) | 断言属性包含 3 个项目 |
assertSee('...') | 断言渲染的 HTML 包含提供的文本 |
assertDontSee('...') | 断言渲染的 HTML 不包含提供的文本 |
assertSeeHtml('<div>...</div>') | 断言原始 HTML 出现在渲染输出中 |
assertDontSeeHtml('<div>...</div>') | 断言原始 HTML 不出现在渲染输出中 |
assertSeeInOrder(['first', 'second']) | 断言字符串按顺序出现在渲染输出中 |
assertDispatched('post-created') | 断言事件已被分发 |
assertNotDispatched('post-created') | 断言事件未被分发 |
assertHasErrors('title') | 断言属性的验证失败 |
assertHasErrors(['title' => ['required', 'min:6']]) | 断言特定验证规则失败 |
assertHasNoErrors('title') | 断言属性没有验证错误 |
assertRedirect() | 断言触发了重定向 |
assertRedirect('/posts') | 断言重定向到特定 URL |
assertRedirectToRoute('posts.index') | 断言重定向到命名路由 |
assertNoRedirect() | 断言没有触发重定向 |
assertViewHas('posts') | 断言数据已传递给视图 |
assertViewHas('postCount', 3) | 断言视图数据具有特定值 |
assertViewHas('posts', function ($posts) { ... }) | 断言视图数据通过自定义验证 |
assertViewIs('livewire.show-posts') | 断言渲染了特定视图 |
assertFileDownloaded() | 断言触发了文件下载 |
assertFileDownloaded($filename) | 断言下载了特定文件 |
assertUnauthorized() | 断言抛出了授权异常 (401) |
assertForbidden() | 断言访问被禁止 (403) |
assertStatus(500) | 断言返回了特定状态码 |