主题
测试
创建第一个测试
通过在 make:livewire 命令后附加 --test 标志,你可以在生成组件的同时生成测试文件:
shell
php artisan make:livewire post.create --test除了生成组件文件本身外,上述命令还将生成以下测试文件 tests/Feature/Livewire/Post/CreateTest.php:
如果你想创建一个 Pest PHP 测试,可以在 make:livewire 命令中提供 --pest 选项:
php
<?php
namespace Tests\Feature\Livewire\Post;
use Livewire\Livewire;
use Tests\TestCase;
class CreateTest extends TestCase
{
public function test_renders_successfully()
{
Livewire::test('post.create')
->assertStatus(200);
}
}当然,你也可以手动创建这些文件,甚至可以在 Laravel 应用程序中任何其他现有的 PHPUnit 测试中使用 Livewire 的测试工具。
在继续阅读之前,你可能希望熟悉 Laravel 自己的内置测试功能。
测试页面包含组件
你可以编写的最简单的 Livewire 测试是断言应用程序中的给定端点包含并成功渲染给定的 Livewire 组件。
Livewire 提供了一个 assertSeeLivewire() 方法,可以从任何 Laravel 测试中使用:
php
<?php
namespace Tests\Feature\Livewire\Post;
use Tests\TestCase;
class CreateTest extends TestCase
{
public function test_component_exists_on_the_page()
{
$this->get('/posts/create')
->assertSeeLivewire('post.create');
}
}这些被称为冒烟测试
冒烟测试是广泛的测试,确保应用程序中没有灾难性问题。虽然它看起来像是不值得编写的测试,但从性价比来说,这些是你可以编写的一些最有价值的测试,因为它们几乎不需要维护,并为你提供应用程序将成功渲染且没有重大错误的基本信心。
测试视图
Livewire 提供了一个简单而强大的工具来断言组件渲染输出中文本的存在:assertSee()。
以下是使用 assertSee() 确保数据库中所有帖子都显示在页面上的示例:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\ShowPosts;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class ShowPostsTest extends TestCase
{
public function test_displays_posts()
{
Post::factory()->make(['title' => 'On bathing well']);
Post::factory()->make(['title' => 'There\'s no time like bathtime']);
Livewire::test(ShowPosts::class)
->assertSee('On bathing well')
->assertSee('There\'s no time like bathtime');
}
}断言视图数据
除了断言渲染视图的输出外,有时测试传递到视图中的数据也很有帮助。
以下是与上面相同的测试,但测试的是视图数据而不是渲染输出:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\ShowPosts;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class ShowPostsTest extends TestCase
{
public function test_displays_all_posts()
{
Post::factory()->make(['title' => 'On bathing well']);
Post::factory()->make(['title' => 'The bathtub is my sanctuary']);
Livewire::test(ShowPosts::class)
->assertViewHas('posts', function ($posts) {
return count($posts) == 2;
});
}
}如你所见,assertViewHas() 提供了对指定数据进行断言的控制。
如果你宁愿进行简单的断言,例如确保一条视图数据与给定值匹配,你可以将该值直接作为第二个参数传递给 assertViewHas() 方法。
例如,假设你有一个组件,其中有一个名为 $postCount 的变量被传递到视图中,你可以像这样对其字面值进行断言:
php
$this->assertViewHas('postCount', 3)设置认证用户
大多数 Web 应用程序需要用户在使用之前登录。Livewire 提供了一个 actingAs() 方法,而不是在测试开始时手动认证一个虚假用户。
以下是一个测试示例,其中多个用户有帖子,但认证用户应该只能看到自己的帖子:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\ShowPosts;
use Livewire\Livewire;
use App\Models\User;
use App\Models\Post;
use Tests\TestCase;
class ShowPostsTest extends TestCase
{
public function test_user_only_sees_their_own_posts()
{
$user = User::factory()
->has(Post::factory()->count(3))
->create();
$stranger = User::factory()
->has(Post::factory()->count(2))
->create();
Livewire::actingAs($user)
->test(ShowPosts::class)
->assertViewHas('posts', function ($posts) {
return count($posts) == 3;
});
}
}测试属性
Livewire 还提供了有用的测试工具用于设置和断言组件内的属性。
当用户与包含 wire:model 的表单输入交互时,组件属性通常会在应用程序中更新。但是,由于测试通常不会在实际浏览器中输入,Livewire 允许你使用 set() 方法直接设置属性。
以下是使用 set() 更新 post.create 组件的 $title 属性的示例:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use Tests\TestCase;
class PostCreateTest extends TestCase
{
public function test_can_set_title()
{
Livewire::test('post.create')
->set('title', 'Confessions of a serial soaker')
->assertSet('title', 'Confessions of a serial soaker');
}
}初始化属性
通常,Livewire 组件从父组件或路由参数接收传递的数据。由于 Livewire 组件是孤立测试的,你可以使用 Livewire::test() 方法的第二个参数手动将数据传递给它们:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\UpdatePost;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class UpdatePostTest extends TestCase
{
public function test_title_field_is_populated()
{
$post = Post::factory()->make([
'title' => 'Top ten bath bombs',
]);
Livewire::test(UpdatePost::class, ['post' => $post])
->assertSet('title', 'Top ten bath bombs');
}
}被测试的底层组件(post.edit)将通过其 mount() 方法接收 $post。让我们查看 post.edit 的源代码以更清晰地了解此功能:
php
<?php // resources/views/components/post/⚡edit.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component
{
public Post $post;
public $title = '';
public function mount(Post $post)
{
$this->post = $post;
$this->title = $post->title;
}
// ...
};设置 URL 参数
如果你的 Livewire 组件依赖于其加载页面 URL 中的特定查询参数,你可以使用 withQueryParams() 方法为测试手动设置查询参数。
以下是一个基本的 search-posts 组件,它使用 Livewire 的 URL 功能来存储和跟踪查询字符串中的当前搜索查询:
php
<?php // resources/views/components/⚡search-posts.blade.php
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use App\Models\Post;
new class extends Component
{
#[Url] // [tl! highlight]
public $search = '';
#[Computed]
public function posts()
{
return Post::search($this->search)->get(),
}
};
?>
<div>
@foreach ($this->posts as $post)
{{ $post->title }}
@endforeach
</div>如你所见,上面的 $search 属性使用 Livewire 的 #[Url] 属性来表明其值应该存储在 URL 中。
以下是如何模拟在具有特定查询参数的 URL 页面上加载此组件的场景的示例:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\SearchPosts;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class SearchPostsTest extends TestCase
{
public function test_can_search_posts_via_url_query_string()
{
Post::factory()->create(['title' => 'Testing the first water-proof hair dryer']);
Post::factory()->create(['title' => 'Rubber duckies that actually float']);
Livewire::withQueryParams(['search' => 'hair'])
->test(SearchPosts::class)
->assertSee('Testing the first')
->assertDontSee('Rubber duckies');
}
}设置 cookies
如果你的 Livewire 组件依赖于 cookies,你可以使用 withCookie() 或 withCookies() 方法为测试手动设置 cookies。
以下是一个基本的 cart 组件,它在 mount 时从 cookie 加载折扣令牌:
php
<?php // resources/views/components/⚡cart.blade.php
use Livewire\Component;
new class extends Component
{
public $discountToken;
public function mount()
{
$this->discountToken = request()->cookie('discountToken');
}
};
?>
<div>
Discount: {{ $discountToken }}
</div>如你所见,上面的 $discountToken 属性从请求中的 cookie 获取其值。
以下是如何模拟在具有 cookies 的页面上加载此组件的场景的示例:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\Cart;
use Livewire\Livewire;
use Tests\TestCase;
class CartTest extends TestCase
{
public function test_can_load_discount_token_from_a_cookie()
{
Livewire::withCookies(['discountToken' => 'CALEB2023'])
->test(Cart::class)
->assertSet('discountToken', 'CALEB2023');
}
}调用操作
Livewire 操作通常从前端使用类似 wire:click 的方式调用。
由于 Livewire 组件测试不使用实际浏览器,你可以改为使用 call() 方法在测试中触发操作。
以下是一个 post.create 组件使用 call() 方法触发 save() 操作的示例:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use App\Models\Post;
use Tests\TestCase;
class PostCreateTest extends TestCase
{
public function test_can_create_post()
{
$this->assertEquals(0, Post::count());
Livewire::test('post.create')
->set('title', 'Wrinkly fingers? Try this one weird trick')
->set('content', '...')
->call('save');
$this->assertEquals(1, Post::count());
}
}在上面的测试中,我们断言调用 save() 在数据库中创建了一个新帖子。
你也可以通过将额外参数传递给 call() 方法来将参数传递给操作:
php
->call('deletePost', $postId);验证
要测试验证错误是否被抛出,你可以使用 Livewire 的 assertHasErrors() 方法:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use Tests\TestCase;
class PostCreateTest extends TestCase
{
public function test_title_field_is_required()
{
Livewire::test('post.create')
->set('title', '')
->call('save')
->assertHasErrors('title');
}
}如果你想测试特定的验证规则失败,你可以传递一个规则数组:
php
$this->assertHasErrors(['title' => ['required']]);或者如果你想断言存在验证消息,你也可以这样做:
php
$this->assertHasErrors(['title' => ['标题字段是必填的。']]);授权
对依赖于不可信输入的 Livewire 组件中的操作进行授权是至关重要的。Livewire 提供了 assertUnauthorized() 和 assertForbidden() 方法来确保认证或授权检查失败:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\UpdatePost;
use Livewire\Livewire;
use App\Models\User;
use App\Models\Post;
use Tests\TestCase;
class UpdatePostTest extends TestCase
{
public function test_cant_update_another_users_post()
{
$user = User::factory()->create();
$stranger = User::factory()->create();
$post = Post::factory()->for($stranger)->create();
Livewire::actingAs($user)
->test(UpdatePost::class, ['post' => $post])
->set('title', 'Living the lavender life')
->call('save')
->assertUnauthorized();
Livewire::actingAs($user)
->test(UpdatePost::class, ['post' => $post])
->set('title', 'Living the lavender life')
->call('save')
->assertForbidden();
}
}如果你愿意,你也可以使用 assertStatus() 测试组件中的操作可能触发的显式状态代码:
php
->assertStatus(401); // Unauthorized
->assertStatus(403); // Forbidden重定向
你可以使用 assertRedirect() 方法测试 Livewire 操作执行了重定向:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use Tests\TestCase;
class PostCreateTest extends TestCase
{
public function test_redirected_to_all_posts_after_creating_a_post()
{
Livewire::test('post.create')
->set('title', 'Using a loofah doesn\'t make you aloof...ugh')
->set('content', '...')
->call('save')
->assertRedirect('/posts');
}
}作为额外的便利,你可以断言用户被重定向到特定的页面组件,而不是硬编码的 URL。
php
->assertRedirect('/posts');事件
要断言从组件内调度了事件,你可以使用 ->assertDispatched() 方法:
php
<?php
namespace Tests\Feature\Livewire;
use Livewire\Livewire;
use Tests\TestCase;
class PostCreateTest extends TestCase
{
public function test_creating_a_post_dispatches_event()
{
Livewire::test('post.create')
->set('title', 'Top 100 bubble bath brands')
->set('content', '...')
->call('save')
->assertDispatched('post-created');
}
}测试两个组件可以通过调度和监听事件相互通信通常很有帮助。使用 dispatch() 方法,让我们模拟一个 post.create 组件调度了 post-created 事件。然后,我们将断言监听该事件的 PostCountBadge 组件适当地更新了其帖子计数:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\PostCountBadge;
use Livewire\Livewire;
use Tests\TestCase;
class PostCountBadgeTest extends TestCase
{
public function test_post_count_is_updated_when_event_is_dispatched()
{
$badge = Livewire::test(PostCountBadge::class)
->assertSee("0");
Livewire::test('post.create')
->set('title', 'Tear-free: the greatest lie ever told')
->set('content', '...')
->call('save')
->assertDispatched('post-created');
$badge->dispatch('post-created')
->assertSee("1");
}
}有时断言事件是否带有一个或多个参数调度可能会派上用场。让我们查看一个名为 ShowPosts 的组件,它调度了一个名为 banner-message 的事件,并带有一个名为 message 的参数:
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\ShowPosts;
use Livewire\Livewire;
use Tests\TestCase;
class ShowPostsTest extends TestCase
{
public function test_notification_is_dispatched_when_deleting_a_post()
{
Livewire::test(ShowPosts::class)
->call('delete', postId: 3)
->assertDispatched('notify',
message: 'The post was deleted',
);
}
}如果你的组件调度的事件的参数值必须有条件地断言,你可以将闭包作为第二个参数传递给 assertDispatched 方法,如下所示。它接收事件名称作为第一个参数,并接收包含参数的数组作为第二个参数。确保闭包返回布尔值。
php
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\ShowPosts;
use Livewire\Livewire;
use Tests\TestCase;
class ShowPostsTest extends TestCase
{
public function test_notification_is_dispatched_when_deleting_a_post()
{
Livewire::test(ShowPosts::class)
->call('delete', postId: 3)
->assertDispatched('notify', function($eventName, $params) {
return ($params['message'] ?? '') === 'The post was deleted';
})
}
}所有可用的测试工具
Livewire 提供了更多的测试工具。以下是所有可用测试方法的综合列表,并附有关于如何使用它们的简短描述:
设置方法
| 方法 | 描述 |
|---|---|
Livewire::test('post.create') | 测试 post.create 组件 |
Livewire::test(UpdatePost::class, ['post' => $post]) | 使用 post 参数测试 UpdatePost 组件(通过 mount() 方法接收) |
Livewire::actingAs($user) | 将提供的用户设置为会话的认证用户 |
Livewire::withQueryParams(['search' => '...']) | 将测试的 search URL 查询参数设置为提供的值(例如 ?search=...)。通常在使用 Livewire #[Url] 属性的属性上下文中使用 |
Livewire::withCookie('color', 'blue') | 将测试的 color cookie 设置为提供的值(blue) |
Livewire::withCookies(['color' => 'blue', 'name' => 'Taylor]) | 将测试的 color 和 name cookies 设置为提供的值(blue、Taylor) |
Livewire::withHeaders(['X-COLOR' => 'blue', 'X-NAME' => 'Taylor]) | 将测试的 X-COLOR 和 X-NAME 标头设置为提供的值(blue、Taylor) |
Livewire::withoutLazyLoading() | 在此测试的组件及所有子组件中禁用延迟加载 |
与组件交互
| 方法 | 描述 |
|---|---|
set('title', '...') | 将 title 属性设置为提供的值 |
set(['title' => '...', ...]) | 使用关联数组设置多个组件属性 |
toggle('sortAsc') | 在 true 和 false 之间切换 sortAsc 属性 |
call('save') | 调用 save 操作/方法 |
call('remove', $post->id) | 调用 remove 方法并将 $post->id 作为第一个参数传递(也接受后续参数) |
refresh() | 触发组件重新渲染 |
dispatch('post-created') | 从组件调度 post-created 事件 |
dispatch('post-created', postId: $post->id) | 调度 post-created 事件并将 $post->id 作为额外参数(来自 Alpine 的 $event.detail) |
断言
| 方法 | 描述 |
|---|---|
assertSet('title', '...') | 断言 title 属性被设置为提供的值 |
assertNotSet('title', '...') | 断言 title 属性未被设置为提供的值 |
assertSetStrict('title', '...') | 使用严格比较断言 title 属性被设置为提供的值 |
assertNotSetStrict('title', '...') | 使用严格比较断言 title 属性未被设置为提供的值 |
assertReturned('...') | 断言之前的 ->call(...) 返回了给定的值 |
assertCount('posts', 3) | 断言 posts 属性是包含 3 个项目的类数组值 |
assertSnapshotSet('date', '08/26/1990') | 断言 date 属性的原始/脱水值(来自 JSON)被设置为 08/26/1990。作为断言水合的 DateTime 实例的替代方案 |
assertSnapshotNotSet('date', '08/26/1990') | 断言 date 的原始/脱水值不等于提供的值 |
assertSee($post->title) | 断言组件的渲染 HTML 包含提供的值 |
assertDontSee($post->title) | 断言渲染的 HTML 不包含提供的值 |
assertSeeHtml('<div>...</div>') | 断言渲染的 HTML 中包含提供的字符串字面量,不转义 HTML 字符(与默认转义提供字符的 assertSee 不同) |
assertDontSeeHtml('<div>...</div>') | 断言渲染的 HTML 中不包含提供的字符串 |
assertSeeText($post->title) | 断言渲染的 HTML 文本中包含提供的字符串。在进行断言之前,渲染的内容将传递给 strip_tags PHP 函数 |
assertDontSeeText($post->title) | 断言渲染的 HTML 文本中不包含提供的字符串。在进行断言之前,渲染的内容将传递给 strip_tags PHP 函数 |
assertSeeInOrder(['...', '...']) | 断言提供的字符串按顺序出现在组件的渲染 HTML 输出中 |
assertSeeHtmlInOrder([$firstString, $secondString]) | 断言提供的 HTML 字符串按顺序出现在组件的渲染输出中 |
assertDispatched('post-created') | 断言组件已调度给定事件 |
assertNotDispatched('post-created') | 断言组件未调度给定事件 |
assertHasErrors('title') | 断言 title 属性的验证失败 |
assertHasErrors(['title' => ['required', 'min:6']]) | 断言 title 属性的提供的验证规则失败 |
assertHasNoErrors('title') | 断言 title 属性没有验证错误 |
assertHasNoErrors(['title' => ['required', 'min:6']]) | 断言 title 属性的提供的验证规则未失败 |
assertRedirect() | 断言组件内已触发重定向 |
assertRedirect('/posts') | 断言组件触发了到 /posts 端点的重定向 |
assertRedirect(ShowPosts::class) | 断言组件触发了到 ShowPosts 组件的重定向 |
assertRedirectToRoute('name', ['parameters']) | 断言组件触发了到给定路由的重定向 |
assertNoRedirect() | 断言未触发重定向 |
assertViewHas('posts') | 断言 render() 方法已将 posts 项传递给视图数据 |
assertViewHas('postCount', 3) | 断言已将值为 3 的 postCount 变量传递给视图 |
assertViewHas('posts', function ($posts) { ... }) | 断言 posts 视图数据存在,并且它通过提供的回调中声明的任何断言 |
assertViewIs('livewire.show-posts') | 断言组件的 render 方法返回了提供的视图名称 |
assertFileDownloaded() | 断言已触发文件下载 |
assertFileDownloaded($filename) | 断言已触发与提供的文件名匹配的文件下载 |
assertNoFileDownloaded() | 断言未触发文件下载 |
assertUnauthorized() | 断言组件内抛出了授权异常(状态代码:401) |
assertForbidden() | 断言触发了状态代码为 403 的错误响应 |
assertStatus(500) | 断言最新响应与提供的状态代码匹配 |