测试

您是视觉学习者吗?
通过我们深入的屏幕录制掌握 Livewire
立即观看

创建您的第一个测试

通过将 --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]) 将测试的 colorname cookie 设置为提供的值(blueTaylor)。
Livewire::withHeaders(['X-COLOR' => 'blue', 'X-NAME' => 'Taylor]) 将测试的 X-COLORX-NAME 标头设置为提供的值(blueTaylor)。
Livewire::withoutLazyLoading() 在此测试中的所有子组件中禁用延迟加载。

与组件交互

方法 说明
set('title', '...') title 属性设置为提供的值
set(['title' => '...', ...]) 使用关联数组设置多个组件属性
toggle('sortAsc') truefalse 之间切换 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) 断言最新的响应与提供的状态代码匹配