> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP 测试

> 介绍如何使用 Laravel 的 HTTP 测试能力模拟请求、编写断言、进行认证测试和文件上传测试。

## 简介

Laravel 提供了丰富的 API 用于模拟 HTTP 请求并对响应进行断言。
你无需实际启动 HTTP 服务器，就能在测试中重现应用的请求处理流程。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('应用返回成功响应', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_the_application_returns_a_successful_response(): void
      {
          $response = $this->get('/');

          $response->assertStatus(200);
      }
  }
  ```
</CodeGroup>

`get()` 方法会向应用发送一个 `GET` 请求，`assertStatus()` 用于校验返回的 HTTP 状态码。

<Info>
  测试运行期间 CSRF 中间件会被自动禁用，不需要在测试中显式关闭。
</Info>

## 发起请求

在测试中，可以使用 `get`、`post`、`put`、`patch`、`delete` 等方法发起请求。
这些方法并不会真正发起网络请求，而是在应用内部模拟。
返回值是 `Illuminate\Testing\TestResponse` 的实例，提供了大量的断言方法。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('基本请求', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_a_basic_request(): void
      {
          $response = $this->get('/');

          $response->assertStatus(200);
      }
  }
  ```
</CodeGroup>

<Warning>
  建议每个测试方法内基本上只发一次请求。在同一个测试中执行多个请求可能会出现意想不到的行为。
</Warning>

### 自定义请求头

使用 `withHeaders()` 可以自定义请求头。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('带请求头的请求', function () {
      $response = $this->withHeaders([
          'X-Header' => 'Value',
      ])->post('/user', ['name' => 'Sally']);

      $response->assertStatus(201);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_interacting_with_headers(): void
      {
          $response = $this->withHeaders([
              'X-Header' => 'Value',
          ])->post('/user', ['name' => 'Sally']);

          $response->assertStatus(201);
      }
  }
  ```
</CodeGroup>

### Cookie

在发起请求前，可以通过 `withCookie()` 或 `withCookies()` 设置 Cookie 值。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('带 Cookie 的请求', function () {
      $response = $this->withCookie('color', 'blue')->get('/');

      $response = $this->withCookies([
          'color' => 'blue',
          'name'  => 'Taylor',
      ])->get('/');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_interacting_with_cookies(): void
      {
          $response = $this->withCookie('color', 'blue')->get('/');

          $response = $this->withCookies([
              'color' => 'blue',
              'name'  => 'Taylor',
          ])->get('/');
      }
  }
  ```
</CodeGroup>

### 会话与认证

`withSession()` 可以在请求前设置 Session 数据。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('带 Session 的请求', function () {
      $response = $this->withSession(['banned' => false])->get('/');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_interacting_with_the_session(): void
      {
          $response = $this->withSession(['banned' => false])->get('/');
      }
  }
  ```
</CodeGroup>

`actingAs()` 可以让你以已认证用户的身份发起请求。通常与模型工厂配合使用。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use App\Models\User;

  test('需要认证的操作', function () {
      $user = User::factory()->create();

      $response = $this->actingAs($user)
          ->withSession(['banned' => false])
          ->get('/');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use App\Models\User;
  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_an_action_that_requires_authentication(): void
      {
          $user = User::factory()->create();

          $response = $this->actingAs($user)
              ->withSession(['banned' => false])
              ->get('/');
      }
  }
  ```
</CodeGroup>

给 `actingAs()` 的第二个参数传入 guard 名，即可以该 guard 进行认证，测试期间该 guard 会成为默认 guard。

```php theme={null}
$this->actingAs($user, 'web');
```

若要以未认证状态发起请求，使用 `actingAsGuest()`。

```php theme={null}
$this->actingAsGuest();
```

### 调试响应

测试中查看响应内容，可以使用 `dump`、`dumpHeaders`、`dumpSession` 方法。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('响应调试', function () {
      $response = $this->get('/');

      $response->dump();
      $response->dumpHeaders();
      $response->dumpSession();
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_debug_response(): void
      {
          $response = $this->get('/');

          $response->dump();
          $response->dumpHeaders();
          $response->dumpSession();
      }
  }
  ```
</CodeGroup>

想要在打印后立即停止执行，可以使用 `dd`、`ddHeaders`、`ddBody`、`ddJson`、`ddSession`。

```php theme={null}
$response->dd();
$response->ddHeaders();
$response->ddBody();
$response->ddJson();
$response->ddSession();
```

### 异常测试

要测试是否抛出特定异常，可以使用 `Exceptions` Facade。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use App\Exceptions\InvalidOrderException;
  use Illuminate\Support\Facades\Exceptions;

  test('会抛出异常', function () {
      Exceptions::fake();

      $response = $this->get('/order/1');

      // 断言抛出过异常
      Exceptions::assertReported(InvalidOrderException::class);

      // 校验异常内容
      Exceptions::assertReported(function (InvalidOrderException $e) {
          return $e->getMessage() === 'The order was invalid.';
      });
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use App\Exceptions\InvalidOrderException;
  use Illuminate\Support\Facades\Exceptions;
  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_exception_is_thrown(): void
      {
          Exceptions::fake();

          $response = $this->get('/');

          Exceptions::assertReported(InvalidOrderException::class);

          Exceptions::assertReported(function (InvalidOrderException $e) {
              return $e->getMessage() === 'The order was invalid.';
          });
      }
  }
  ```
</CodeGroup>

若要断言没有抛出异常，可以使用 `assertNotReported` 或 `assertNothingReported`。

```php theme={null}
Exceptions::assertNotReported(InvalidOrderException::class);

Exceptions::assertNothingReported();
```

若想在关闭异常处理的情况下发起请求，可以使用 `withoutExceptionHandling()`。

```php theme={null}
$response = $this->withoutExceptionHandling()->get('/');
```

要测试闭包中的代码是否会抛出异常，可以使用 `assertThrows()`。

```php theme={null}
$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    OrderInvalid::class
);

// 同时校验异常内容
$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    fn (OrderInvalid $e) => $e->orderId() === 123
);
```

若要断言不会抛出异常，可以使用 `assertDoesntThrow()`。

```php theme={null}
$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute());
```

## 测试 JSON API

Laravel 提供了大量用于 JSON API 测试的辅助方法。
可以通过 `json`、`getJson`、`postJson`、`putJson`、`patchJson`、`deleteJson`、`optionsJson` 等方法发起 JSON 请求。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('发送 API 请求', function () {
      $response = $this->postJson('/api/user', ['name' => 'Sally']);

      $response
          ->assertStatus(201)
          ->assertJson([
              'created' => true,
          ]);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_making_an_api_request(): void
      {
          $response = $this->postJson('/api/user', ['name' => 'Sally']);

          $response
              ->assertStatus(201)
              ->assertJson([
                  'created' => true,
              ]);
      }
  }
  ```
</CodeGroup>

JSON 响应中的数据也可以以数组变量方式访问。

<CodeGroup>
  ```php Pest theme={null}
  expect($response['created'])->toBeTrue();
  ```

  ```php PHPUnit theme={null}
  $this->assertTrue($response['created']);
  ```
</CodeGroup>

<Info>
  `assertJson()` 会把响应转换为数组，判断指定数组是否包含在 JSON 响应中。即便 JSON 中还有其他字段，只要包含指定片段就会通过。
</Info>

### 完全一致断言

`assertExactJson()` 可以断言返回的 JSON 与指定数组**完全一致**。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('完全一致的 JSON 断言', function () {
      $response = $this->postJson('/user', ['name' => 'Sally']);

      $response
          ->assertStatus(201)
          ->assertExactJson([
              'created' => true,
          ]);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_asserting_an_exact_json_match(): void
      {
          $response = $this->postJson('/user', ['name' => 'Sally']);

          $response
              ->assertStatus(201)
              ->assertExactJson([
                  'created' => true,
              ]);
      }
  }
  ```
</CodeGroup>

### JSON 路径断言

`assertJsonPath()` 可以校验指定路径下的值。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('JSON 路径断言', function () {
      $response = $this->postJson('/user', ['name' => 'Sally']);

      $response
          ->assertStatus(201)
          ->assertJsonPath('team.owner.name', 'Darian');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_asserting_a_json_paths_value(): void
      {
          $response = $this->postJson('/user', ['name' => 'Sally']);

          $response
              ->assertStatus(201)
              ->assertJsonPath('team.owner.name', 'Darian');
      }
  }
  ```
</CodeGroup>

也可以传入闭包做更灵活的校验。

```php theme={null}
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);
```

### 流式 JSON 测试

给 `assertJson()` 传入闭包后，可以用 `AssertableJson` 实例以链式风格编写断言。

<CodeGroup>
  ```php Pest theme={null}
  use Illuminate\Testing\Fluent\AssertableJson;

  test('流式 JSON 测试', function () {
      $response = $this->getJson('/users/1');

      $response
          ->assertJson(fn (AssertableJson $json) =>
              $json->where('id', 1)
                  ->where('name', 'Victoria Faith')
                  ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                  ->whereNot('status', 'pending')
                  ->missing('password')
                  ->etc()
          );
  });
  ```

  ```php PHPUnit theme={null}
  use Illuminate\Testing\Fluent\AssertableJson;

  public function test_fluent_json(): void
  {
      $response = $this->getJson('/users/1');

      $response
          ->assertJson(fn (AssertableJson $json) =>
              $json->where('id', 1)
                  ->where('name', 'Victoria Faith')
                  ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                  ->whereNot('status', 'pending')
                  ->missing('password')
                  ->etc()
          );
  }
  ```
</CodeGroup>

<Tip>
  `etc()` 表示允许除断言字段之外还存在其他属性。不加 `etc()` 时，一旦有未断言的额外属性测试就会失败。这可以避免无意中把敏感信息暴露在响应中。
</Tip>

要断言属性存在或不存在，使用 `has()` 和 `missing()`。

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->has('data')
        ->missing('message')
);
```

要一次性判断多个属性，使用 `hasAll()` 或 `missingAll()`。

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->hasAll(['status', 'data'])
        ->missingAll(['message', 'code'])
);
```

#### JSON 集合断言

如果路由返回的是包含多个元素的 JSON，可以通过 `has()` 校验数量或元素内容。

```php theme={null}
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has(3)
            ->first(fn (AssertableJson $json) =>
                $json->where('id', 1)
                    ->where('name', 'Victoria Faith')
                    ->missing('password')
                    ->etc()
            )
    );
```

要对每个元素都应用相同断言，使用 `each()`。

```php theme={null}
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has(3)
            ->each(fn (AssertableJson $json) =>
                $json->whereType('id', 'integer')
                    ->whereType('name', 'string')
                    ->whereType('email', 'string')
                    ->missing('password')
                    ->etc()
            )
    );
```

#### JSON 类型断言

使用 `whereType()` 或 `whereAllType()` 可以校验属性的类型。

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('id', 'integer')
        ->whereAllType([
            'users.0.name' => 'string',
            'meta' => 'array'
        ])
);
```

用 `|` 可以指定多个类型，符合其中之一即通过。

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('name', 'string|null')
        ->whereType('id', ['string', 'integer'])
);
```

可用类型有 `string`、`integer`、`double`、`boolean`、`array`、`null`。

## 认证测试

通过 `actingAs()` 可以以已认证用户的身份发起请求。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use App\Models\User;
  use Illuminate\Foundation\Testing\RefreshDatabase;

  uses(RefreshDatabase::class);

  test('已认证用户可以查看仪表盘', function () {
      $user = User::factory()->create();

      $response = $this->actingAs($user)->get('/dashboard');

      $response->assertStatus(200);
  });

  test('未认证用户会被重定向', function () {
      $response = $this->get('/dashboard');

      $response->assertRedirect('/login');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use App\Models\User;
  use Illuminate\Foundation\Testing\RefreshDatabase;
  use Tests\TestCase;

  class DashboardTest extends TestCase
  {
      use RefreshDatabase;

      public function test_authenticated_users_can_view_the_dashboard(): void
      {
          $user = User::factory()->create();

          $response = $this->actingAs($user)->get('/dashboard');

          $response->assertStatus(200);
      }

      public function test_unauthenticated_users_are_redirected(): void
      {
          $response = $this->get('/dashboard');

          $response->assertRedirect('/login');
      }
  }
  ```
</CodeGroup>

若要以指定 guard 认证，可以传入第二个参数。

```php theme={null}
$this->actingAs($user, 'api');
```

### 用户注册流程测试示例

下面是对真实注册接口的测试示例。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use App\Models\User;
  use Illuminate\Foundation\Testing\RefreshDatabase;

  uses(RefreshDatabase::class);

  test('用户可以注册', function () {
      $response = $this->post('/register', [
          'name'                  => 'Test User',
          'email'                 => 'test@example.com',
          'password'              => 'password',
          'password_confirmation' => 'password',
      ]);

      $response->assertRedirect('/dashboard');
      $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
  });

  test('重复邮箱不能注册', function () {
      User::factory()->create(['email' => 'test@example.com']);

      $response = $this->post('/register', [
          'name'                  => 'Test User',
          'email'                 => 'test@example.com',
          'password'              => 'password',
          'password_confirmation' => 'password',
      ]);

      $response->assertSessionHasErrors('email');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use App\Models\User;
  use Illuminate\Foundation\Testing\RefreshDatabase;
  use Tests\TestCase;

  class RegistrationTest extends TestCase
  {
      use RefreshDatabase;

      public function test_user_can_register(): void
      {
          $response = $this->post('/register', [
              'name'                  => 'Test User',
              'email'                 => 'test@example.com',
              'password'              => 'password',
              'password_confirmation' => 'password',
          ]);

          $response->assertRedirect('/dashboard');
          $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
      }

      public function test_duplicate_email_cannot_register(): void
      {
          User::factory()->create(['email' => 'test@example.com']);

          $response = $this->post('/register', [
              'name'                  => 'Test User',
              'email'                 => 'test@example.com',
              'password'              => 'password',
              'password_confirmation' => 'password',
          ]);

          $response->assertSessionHasErrors('email');
      }
  }
  ```
</CodeGroup>

## 会话测试

`withSession()` 可以在发起请求前预设 Session 数据。
`assertSessionHas()` 可以断言 Session 中存在某个值。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('校验 Session 值', function () {
      $response = $this->withSession(['locale' => 'zh'])->get('/');

      $response->assertSessionHas('locale', 'zh');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class SessionTest extends TestCase
  {
      public function test_session_value(): void
      {
          $response = $this->withSession(['locale' => 'zh'])->get('/');

          $response->assertSessionHas('locale', 'zh');
      }
  }
  ```
</CodeGroup>

### Session 断言一览

| 方法                                      | 说明                        |
| --------------------------------------- | ------------------------- |
| `assertSessionHas($key, $value)`        | Session 中存在指定 key 和 value |
| `assertSessionHasAll([...])`            | Session 中存在多个 key/value   |
| `assertSessionHasErrors($keys)`         | Session 中存在校验错误           |
| `assertSessionHasErrorsIn($bag, $keys)` | 指定 error bag 中存在错误        |
| `assertSessionHasNoErrors()`            | Session 中没有校验错误           |
| `assertSessionDoesntHaveErrors()`       | Session 中没有校验错误           |
| `assertSessionMissing($key)`            | Session 中不存在指定 key        |

## 文件上传测试

使用 `Illuminate\Http\UploadedFile` 类的 `fake()` 方法，可以生成用于测试的假文件或假图像。
结合 `Storage` Facade 的 `fake()` 方法，就可以轻松地测试文件上传。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use Illuminate\Http\UploadedFile;
  use Illuminate\Support\Facades\Storage;

  test('可以上传头像', function () {
      Storage::fake('avatars');

      $file = UploadedFile::fake()->image('avatar.jpg');

      $response = $this->post('/avatar', [
          'avatar' => $file,
      ]);

      Storage::disk('avatars')->assertExists($file->hashName());
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Illuminate\Http\UploadedFile;
  use Illuminate\Support\Facades\Storage;
  use Tests\TestCase;

  class AvatarTest extends TestCase
  {
      public function test_avatars_can_be_uploaded(): void
      {
          Storage::fake('avatars');

          $file = UploadedFile::fake()->image('avatar.jpg');

          $response = $this->post('/avatar', [
              'avatar' => $file,
          ]);

          Storage::disk('avatars')->assertExists($file->hashName());
      }
  }
  ```
</CodeGroup>

若要断言文件不存在，使用 `assertMissing()`。

```php theme={null}
Storage::disk('avatars')->assertMissing('missing.jpg');
```

### 自定义假文件

可以指定图像的尺寸和文件大小，便于测试校验规则。

```php theme={null}
// 指定宽、高、文件大小（单位 KB）
UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100);

// 创建任意类型的文件
UploadedFile::fake()->create('document.pdf', $sizeInKilobytes);

// 显式指定 MIME 类型
UploadedFile::fake()->create(
    'document.pdf', $sizeInKilobytes, 'application/pdf'
);
```

## 视图测试

也可以不模拟 HTTP 请求，而直接渲染视图进行测试。
`view()` 方法接收视图名称和可选的数据数组，返回 `Illuminate\Testing\TestView` 实例。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('welcome 视图可以渲染', function () {
      $view = $this->view('welcome', ['name' => 'Taylor']);

      $view->assertSee('Taylor');
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

  class ViewTest extends TestCase
  {
      public function test_a_welcome_view_can_be_rendered(): void
      {
          $view = $this->view('welcome', ['name' => 'Taylor']);

          $view->assertSee('Taylor');
      }
  }
  ```
</CodeGroup>

`TestView` 类提供以下断言方法。

| 方法                            | 说明              |
| ----------------------------- | --------------- |
| `assertSee($value)`           | 视图中包含指定字符串      |
| `assertSeeInOrder([...])`     | 视图中按顺序包含指定字符串   |
| `assertSeeText($value)`       | 视图文本中包含指定字符串    |
| `assertSeeTextInOrder([...])` | 视图文本中按顺序包含指定字符串 |
| `assertDontSee($value)`       | 视图中不包含指定字符串     |
| `assertDontSeeText($value)`   | 视图文本中不包含指定字符串   |

要以字符串形式获取渲染结果，可将 `TestView` 实例强转为字符串。

```php theme={null}
$contents = (string) $this->view('welcome');
```

如果需要向视图传递校验错误，使用 `withViewErrors()`。

```php theme={null}
$view = $this->withViewErrors([
    'name' => ['请输入有效的名字。']
])->view('form');

$view->assertSee('请输入有效的名字。');
```

### 组件测试

`blade()` 方法可以渲染原始 Blade 模板字符串。

```php theme={null}
$view = $this->blade(
    '<x-component :name="$name" />',
    ['name' => 'Taylor']
);

$view->assertSee('Taylor');
```

`component()` 方法用于渲染 Blade 组件，返回 `Illuminate\Testing\TestComponent` 实例。

```php theme={null}
$view = $this->component(Profile::class, ['name' => 'Taylor']);

$view->assertSee('Taylor');
```

## 响应断言一览

以下是 `Illuminate\Testing\TestResponse` 类提供的主要断言方法。

### HTTP 状态

| 方法                               | 说明                        |
| -------------------------------- | ------------------------- |
| `assertOk()`                     | 200 OK                    |
| `assertCreated()`                | 201 Created               |
| `assertAccepted()`               | 202 Accepted              |
| `assertNoContent($status = 204)` | 204 No Content            |
| `assertBadRequest()`             | 400 Bad Request           |
| `assertUnauthorized()`           | 401 Unauthorized          |
| `assertForbidden()`              | 403 Forbidden             |
| `assertNotFound()`               | 404 Not Found             |
| `assertUnprocessable()`          | 422 Unprocessable Entity  |
| `assertTooManyRequests()`        | 429 Too Many Requests     |
| `assertInternalServerError()`    | 500 Internal Server Error |
| `assertStatus($code)`            | 指定状态码                     |
| `assertSuccessful()`             | 2xx 状态码                   |
| `assertClientError()`            | 4xx 状态码                   |
| `assertServerError()`            | 5xx 状态码                   |

### 重定向

| 方法                                            | 说明              |
| --------------------------------------------- | --------------- |
| `assertRedirect($uri)`                        | 重定向到指定 URI      |
| `assertRedirectContains($string)`             | 重定向 URI 包含指定字符串 |
| `assertRedirectToRoute($name, $params)`       | 重定向到指定路由        |
| `assertRedirectToSignedRoute($name, $params)` | 重定向到指定签名路由      |
| `assertRedirectBack()`                        | 重定向回上一个 URL     |

### 内容

| 方法                                   | 说明         |
| ------------------------------------ | ---------- |
| `assertSee($value, $escaped = true)` | 响应中包含指定值   |
| `assertSeeInOrder(array $values)`    | 按顺序包含多个值   |
| `assertSeeText($value)`              | 响应文本包含指定值  |
| `assertDontSee($value)`              | 响应中不包含指定值  |
| `assertDontSeeText($value)`          | 响应文本不包含指定值 |
| `assertContent($value)`              | 响应体与指定值一致  |
| `assertDownload($filename)`          | 是下载响应      |

### JSON

| 方法                                         | 说明                       |
| ------------------------------------------ | ------------------------ |
| `assertJson(array $data)`                  | JSON 响应包含指定数据            |
| `assertExactJson(array $data)`             | JSON 响应与指定数据完全一致         |
| `assertJsonPath($path, $value)`            | 指定路径下的值一致                |
| `assertJsonMissingPath($path)`             | 指定路径不存在                  |
| `assertJsonStructure(array $structure)`    | JSON 具有指定结构              |
| `assertJsonCount($count, $key)`            | 指定 key 下的 JSON 具有指定数量的元素 |
| `assertJsonFragment(array $data)`          | JSON 包含指定片段              |
| `assertJsonMissing(array $data)`           | JSON 不包含指定数据             |
| `assertJsonIsArray()`                      | JSON 是数组                 |
| `assertJsonIsObject()`                     | JSON 是对象                 |
| `assertJsonValidationErrors($keys)`        | 包含指定 key 的校验错误           |
| `assertJsonMissingValidationErrors($keys)` | 不包含指定 key 的校验错误          |

### 请求头与 Cookie

| 方法                                  | 说明              |
| ----------------------------------- | --------------- |
| `assertHeader($headerName, $value)` | 指定请求头存在         |
| `assertHeaderMissing($headerName)`  | 指定请求头不存在        |
| `assertCookie($name, $value)`       | 指定 Cookie 存在    |
| `assertCookieExpired($name)`        | 指定 Cookie 已过期   |
| `assertCookieMissing($name)`        | 指定 Cookie 不存在   |
| `assertPlainCookie($name, $value)`  | 指定未加密 Cookie 存在 |

### 视图

| 方法                              | 说明         |
| ------------------------------- | ---------- |
| `assertViewIs($value)`          | 返回的是指定视图   |
| `assertViewHas($key, $value)`   | 视图中存在指定数据  |
| `assertViewHasAll(array $data)` | 视图中存在多个数据  |
| `assertViewMissing($key)`       | 视图中不存在指定数据 |

### 校验

| 方法                     | 说明            |
| ---------------------- | ------------- |
| `assertValid($keys)`   | 指定 key 没有校验错误 |
| `assertInvalid($keys)` | 指定 key 存在校验错误 |

## 实践 TDD 的要点

<Tip>
  HTTP 测试与 TDD（测试驱动开发）非常搭配。留意以下要点会让 TDD 更高效。
</Tip>

<AccordionGroup>
  <Accordion title="从 Feature 测试开始">
    HTTP 测试应放在 `tests/Feature/` 目录下。先定义从应用外部看到的行为（请求 → 响应），可以让待实现的功能更清晰。
  </Accordion>

  <Accordion title="使用 RefreshDatabase">
    需要用到数据库的测试请使用 `RefreshDatabase` trait。每个测试后都会重置数据库，防止用例之间的数据干扰。测试的独立性可以让测试套件更稳定，不依赖执行顺序。
  </Accordion>

  <Accordion title="积极使用工厂">
    使用 `User::factory()->create()` 这类模型工厂能极大简化数据准备。为常用状态定义 factory state 还能提升测试可读性。
  </Accordion>

  <Accordion title="一个测试一个断言">
    每个测试方法尽量只验证一件事。这样在失败时更容易定位原因。遵循 Arrange（准备）→ Act（执行）→ Assert（断言）的 AAA 结构会更清晰。
  </Accordion>

  <Accordion title="不要忘了认证测试">
    对需要认证的路由，务必同时编写“已认证”和“未认证”的测试，以尽早发现安全问题。
  </Accordion>
</AccordionGroup>

## 相关页面

<Card title="测试入门" icon="flask" href="/zh/testing">
  了解 Laravel 中编写测试的基础，以及 `php artisan test` 的使用。
</Card>


## Related topics

- [测试入门](/zh/testing.md)
- [用 Pest PHP 开始 Laravel 测试](/zh/blog/pest-introduction.md)
- [Mock](/zh/mocking.md)
- [使用 Pest 进行进阶测试](/zh/advanced/testing-pest.md)
- [引擎 API 集成应用开发指南 - VOICEVOX for Laravel](/zh/packages/laravel-voicevox/app-guide.md)
