Skip to content

测试

创建第一个测试

通过在 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])将测试的 colorname cookies 设置为提供的值(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。作为断言水合的 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)断言已将值为 3postCount 变量传递给视图
assertViewHas('posts', function ($posts) { ... })断言 posts 视图数据存在,并且它通过提供的回调中声明的任何断言
assertViewIs('livewire.show-posts')断言组件的 render 方法返回了提供的视图名称
assertFileDownloaded()断言已触发文件下载
assertFileDownloaded($filename)断言已触发与提供的文件名匹配的文件下载
assertNoFileDownloaded()断言未触发文件下载
assertUnauthorized()断言组件内抛出了授权异常(状态代码:401)
assertForbidden()断言触发了状态代码为 403 的错误响应
assertStatus(500)断言最新响应与提供的状态代码匹配