Skip to content
全部文档

测试

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');
});

TIP

冒烟测试价值很大

这类测试称为「冒烟测试」——它们确保应用中没有灾难性的问题。虽然简单,但维护成本很低,又能给你一个基础信心:页面能够成功渲染,因此价值巨大。

浏览器测试

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::test(),你还可以用 Livewire::visit() 在真实浏览器中测试组件:

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 浏览器测试断言

INFO

何时使用浏览器测试

将浏览器测试用于关键用户流程和复杂交互。对于大多数组件测试,标准的 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');
});

设置 Cookie

使用 withCookie()withCookies() 为测试设置 cookie:

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);
});

TIP

Pest 期望(expectations)

上面的示例使用 Pest 的 expect() 语法做断言。可用期望的完整列表,请参阅 Pest expectations 文档

你也可以向操作传递参数:

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';
        });
});

测试 JS 求值

断言组件通过 $this->js() 执行了 JavaScript:

php
it('shows an alert after saving', function () {
    Livewire::test('post.create')
        ->set('title', 'New post')
        ->call('save')
        ->assertJs("alert('Post saved!')");
});

你也可以断言没有执行任何 JS:

php
->assertNoJs();

使用 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 的。

TIP

不妨试试 Pest

如果你想了解 Pest 更优雅的语法与功能,可前往 pestphp.com 了解更多。

全部可用的测试方法

以下是你可用的全部 Livewire 测试方法的完整参考:

设置方法

方法说明
Livewire::test('post.create')测试 post.create 组件
Livewire::test(UpdatePost::class, ['post' => $post])测试 UpdatePost 组件,并向 mount() 传入参数
Livewire::actingAs($user)为测试设置已认证用户
Livewire::withQueryParams(['search' => '...'])设置 URL 查询参数(例如 ?search=...
Livewire::withCookie('name', 'value')为测试设置一个 cookie
Livewire::withCookies(['color' => 'blue', 'name' => 'Taylor'])设置多个 cookie
Livewire::withHeaders(['X-Header' => 'value'])设置自定义请求头
Livewire::withoutLazyLoading()在本测试中禁用所有组件的懒加载

与组件交互

方法说明
set('title', '...')title 属性设为给定值
set(['title' => '...', 'content' => '...'])使用数组设置多个属性
toggle('sortAsc')truefalse 之间切换布尔属性
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')断言渲染了特定视图
assertJs("alert('...')")断言已执行某个 JS 表达式
assertNoJs()断言未执行任何 JS
assertFileDownloaded()断言已触发文件下载
assertFileDownloaded($filename)断言已下载特定文件
assertUnauthorized()断言抛出了授权异常(401)
assertForbidden()断言访问被禁止(403)
assertStatus(500)断言返回了特定状态码

另见

  • 操作测试组件操作与交互
  • 表单测试表单提交与验证
  • 事件测试事件派发与监听
  • 组件创建可测试的组件结构