测试
创建您的第一个测试
通过将 --test
标志附加到 make:livewire
命令,您可以生成一个测试文件和一个组件
php artisan make:livewire create-post --test
除了生成组件文件本身之外,上述命令还将生成以下测试文件 tests/Feature/Livewire/CreatePostTest.php
如果您想创建一个 Pest PHP 测试,您可以向 make:livewire 命令提供 --pest
选项
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function renders_successfully() { Livewire::test(CreatePost::class) ->assertStatus(200); }}
当然,您始终可以通过手动创建这些文件,甚至可以在 Laravel 应用程序中的任何其他现有 PHPUnit 测试中使用 Livewire 的测试实用程序。
在继续阅读之前,您可能希望熟悉 Laravel 自带的测试功能。
测试页面是否包含组件
您可以编写的最简单的 Livewire 测试是断言应用程序中的给定端点包含并成功渲染给定的 Livewire 组件。
Livewire 提供了一个 assertSeeLivewire()
方法,可从任何 Laravel 测试中使用
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function component_exists_on_the_page() { $this->get('/posts/create') ->assertSeeLivewire(CreatePost::class); }}
冒烟测试是广泛的测试,可确保您的应用程序中不存在灾难性问题。虽然它看起来像是不值得编写的测试,但就性价比而言,这些是您可以编写的最有价值的测试,因为它们需要很少的维护,并为您提供应用程序将成功渲染且没有重大错误的基本信心。
测试视图
Livewire 提供了一个简单但功能强大的实用程序,用于断言组件渲染输出中是否存在文本:assertSee()
。
下面是使用 assertSee()
来确保数据库中的所有帖子都显示在页面上的示例
<?php namespace Tests\Feature\Livewire; use App\Livewire\ShowPosts;use Livewire\Livewire;use App\Models\Post;use Tests\TestCase; class ShowPostsTest extends TestCase{ /** @test */ public function 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 namespace Tests\Feature\Livewire; use App\Livewire\ShowPosts;use Livewire\Livewire;use App\Models\Post;use Tests\TestCase; class ShowPostsTest extends TestCase{ /** @test */ public function 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
的变量被传递到视图中,你可以像这样对其字面值进行断言
$this->assertViewHas('postCount', 3)
设置经过身份验证的用户
大多数 Web 应用程序要求用户在使用之前登录。Livewire 提供了一个 actingAs()
方法,而不是在测试开始时手动验证一个假用户。
下面是一个测试示例,其中多个用户有帖子,但经过身份验证的用户只能看到自己的帖子
<?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{ /** @test */ public function 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()
更新 CreatePost
组件的 $title
属性的示例
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function can_set_title() { Livewire::test(CreatePost::class) ->set('title', 'Confessions of a serial soaker') ->assertSet('title', 'Confessions of a serial soaker'); }}
初始化属性
通常,Livewire 组件会接收从父组件或路由参数中传递的数据。由于 Livewire 组件是独立测试的,因此你可以使用 Livewire::test()
方法的第二个参数手动将数据传递给它们
<?php namespace Tests\Feature\Livewire; use App\Livewire\UpdatePost;use Livewire\Livewire;use App\Models\Post;use Tests\TestCase; class UpdatePostTest extends TestCase{ /** @test */ public function 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'); }}
正在测试的基础组件 (UpdatePost
) 将通过其 mount()
方法接收 $post
。我们来看看 UpdatePost
的源代码,以便更清晰地了解此功能
<?php namespace App\Livewire; use Livewire\Component;use App\Models\Post; class UpdatePost extends Component{ public Post $post; public $title = ''; public function mount(Post $post) { $this->post = $post; $this->title = $post->title; } // ...}
设置 URL 参数
如果你的 Livewire 组件依赖于其加载页面 URL 中的特定查询参数,你可以使用 withQueryParams()
方法为你的测试手动设置查询参数。
下面是一个基本的 SearchPosts
组件,它使用 Livewire 的 URL 功能 来存储和跟踪查询字符串中的当前搜索查询
<?php namespace App\Livewire; use Livewire\Component;use Livewire\With\Url;use App\Models\Post; class SearchPosts extends Component{ #[Url] public $search = ''; public function render() { return view('livewire.search-posts', [ 'posts' => Post::search($this->search)->get(), ]); }}
如你所见,上面的 $search
属性使用 Livewire 的 #[Url]
属性来表示其值应存储在 URL 中。
下面是一个示例,说明如何模拟在 URL 中具有特定查询参数的页面上加载此组件的场景
<?php namespace Tests\Feature\Livewire; use App\Livewire\SearchPosts;use Livewire\Livewire;use App\Models\Post;use Tests\TestCase; class SearchPostsTest extends TestCase{ /** @test */ public function 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'); }}
设置 Cookie
如果你的 Livewire 组件依赖于 Cookie,你可以使用 withCookie()
或 withCookies()
方法为你的测试手动设置 Cookie。
下面是一个基本的 Cart
组件,它在加载时从 Cookie 中加载折扣令牌
<?php namespace App\Livewire; use Livewire\Component;use Livewire\With\Url;use App\Models\Post; class Cart extends Component{ public $discountToken; public mount() { $this->discountToken = request()->cookie('discountToken'); }}
如你所见,上面的 $discountToken
属性从请求中的 Cookie 中获取其值。
下面是一个示例,说明如何模拟在具有 Cookie 的页面上加载此组件的场景
<?php namespace Tests\Feature\Livewire; use App\Livewire\Cart;use Livewire\Livewire;use Tests\TestCase; class CartTest extends TestCase{ /** @test */ public function can_load_discount_token_from_a_cookie() { Livewire::withCookies(['discountToken' => 'CALEB2023']) ->test(Cart::class) ->assertSet('discountToken', 'CALEB2023'); }}
调用操作
Livewire 操作通常使用类似 wire:click
的内容从前端调用。
由于 Livewire 组件测试不使用实际浏览器,因此你可以使用 call()
方法在测试中触发操作。
下面是 CreatePost
组件使用 call()
方法触发 save()
操作的示例
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use App\Models\Post;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function can_create_post() { $this->assertEquals(0, Post::count()); Livewire::test(CreatePost::class) ->set('title', 'Wrinkly fingers? Try this one weird trick') ->set('content', '...') ->call('save'); $this->assertEquals(1, Post::count()); }}
在上面的测试中,我们断言调用 save()
会在数据库中创建一个新帖子。
你还可以通过将其他参数传递到 call()
方法来将参数传递给操作
->call('deletePost', $postId);
验证
要测试是否抛出验证错误,可以使用 Livewire 的 assertHasErrors()
方法
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function title_field_is_required() { Livewire::test(CreatePost::class) ->set('title', '') ->call('save') ->assertHasErrors('title'); }}
如果您想测试特定验证规则是否失败,您可以传递规则数组
$this->assertHasErrors(['title' => ['required']]);
或者如果您希望断言验证消息存在,您也可以这样做
$this->assertHasErrors(['title' => ['The title field is required.']]);
授权
授权在 Livewire 组件中依赖不受信任的输入执行操作是必需的。Livewire 提供了 assertUnauthorized()
和 assertForbidden()
方法来确保身份验证或授权检查失败
<?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{ /** @test */ public function 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()
测试组件中的操作可能触发的显式状态代码
->assertStatus(401); // Unauthorized->assertStatus(403); // Forbidden
重定向
您可以使用 assertRedirect()
方法测试 Livewire 操作是否执行了重定向
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function redirected_to_all_posts_after_creating_a_post() { Livewire::test(CreatePost::class) ->set('title', 'Using a loofah doesn\'t make you aloof...ugh') ->set('content', '...') ->call('save') ->assertRedirect('/posts'); }}
为了增加便利性,您可以断言用户被重定向到特定页面组件而不是硬编码的 URL。
->assertRedirect(CreatePost::class);
事件
要断言事件是从组件内部派发的,可以使用 ->assertDispatched()
方法
<?php namespace Tests\Feature\Livewire; use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class CreatePostTest extends TestCase{ /** @test */ public function creating_a_post_dispatches_event() { Livewire::test(CreatePost::class) ->set('title', 'Top 100 bubble bath brands') ->set('content', '...') ->call('save') ->assertDispatched('post-created'); }}
通过分派和监听事件来测试两个组件是否可以相互通信通常很有用。使用 dispatch()
方法,让我们模拟 CreatePost
组件分派 create-post
事件。然后,我们将断言监听该事件的 PostCountBadge
组件适当地更新其帖子计数
<?php namespace Tests\Feature\Livewire; use App\Livewire\PostCountBadge;use App\Livewire\CreatePost;use Livewire\Livewire;use Tests\TestCase; class PostCountBadgeTest extends TestCase{ /** @test */ public function post_count_is_updated_when_event_is_dispatched() { $badge = Livewire::test(PostCountBadge::class) ->assertSee("0"); Livewire::test(CreatePost::class) ->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 namespace Tests\Feature\Livewire; use App\Livewire\ShowPosts;use Livewire\Livewire;use Tests\TestCase; class ShowPostsTest extends TestCase{ /** @test */ public function notification_is_dispatched_when_deleting_a_post() { Livewire::test(ShowPosts::class) ->call('delete', postId: 3) ->assertDispatched('notify', message: 'The post was deleted', ); }}
如果您的组件分派了一个必须有条件断言其参数值的事件,则可以将闭包作为第二个参数传递给 assertDispatched
方法,如下所示。它将事件名称作为第一个参数接收,并将包含参数的数组作为第二个参数接收。确保闭包返回布尔值。
<?php namespace Tests\Feature\Livewire; use App\Livewire\ShowPosts;use Livewire\Livewire;use Tests\TestCase; class ShowPostsTest extends TestCase{ /** @test */ public function 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(CreatePost::class) |
测试 CreatePost 组件 |
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 cookie 设置为提供的值(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 。在 date 的情况下,断言针对已水化的 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 中 |
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 组件 |
assertNoRedirect() |
断言没有触发重定向 |
assertViewHas('posts') |
断言 render() 方法已将 posts 项传递给视图数据 |
assertViewHas('postCount', 3) |
断言 postCount 变量已传递给视图,其值为 3 |
assertViewHas('posts', function ($posts) { ... }) |
断言 posts 视图数据存在,并且它通过提供的回调中声明的任何断言 |
assertViewIs('livewire.show-posts') |
断言组件的渲染方法返回了提供的视图名称 |
assertFileDownloaded() |
断言触发了文件下载 |
assertFileDownloaded($filename) |
断言触发了与提供的文件名匹配的文件下载 |
assertNoFileDownloaded() |
断言没有触发文件下载 |
assertUnauthorized() |
断言组件内抛出了授权异常(状态代码:401) |
assertForbidden() |
断言触发了错误响应,状态代码为:403 |
assertStatus(500) |
断言最新的响应与提供的状态代码匹配 |