跳到內容

測試

編輯此頁面

每當您編寫新的程式碼行時,您也可能會新增新的錯誤。為了建構更好、更可靠的應用程式,您應該使用功能測試和單元測試來測試您的程式碼。

PHPUnit 測試框架

Symfony 與一個名為 PHPUnit 的獨立函式庫整合,為您提供豐富的測試框架。本文不會涵蓋 PHPUnit 本身,它有自己優秀的文件

在建立您的第一個測試之前,請安裝 symfony/test-pack,它會安裝一些測試所需的其他套件 (例如 phpunit/phpunit)

1
$ composer require --dev symfony/test-pack

安裝函式庫後,嘗試執行 PHPUnit

1
$ php bin/phpunit

此命令會自動執行您的應用程式測試。每個測試都是一個以 "Test" 結尾的 PHP 類別 (例如 BlogControllerTest),位於應用程式的 tests/ 目錄中。

PHPUnit 由應用程式根目錄中的 phpunit.xml.dist 檔案設定。Symfony Flex 提供的預設設定在大多數情況下已足夠。請閱讀 PHPUnit 文件,以探索所有可能的設定選項 (例如,啟用程式碼覆蓋率或將測試拆分為多個「測試套件」)。

注意

Symfony Flex 會自動建立 phpunit.xml.disttests/bootstrap.php。如果這些檔案遺失,您可以嘗試使用 composer recipes:install phpunit/phpunit --force -v 再次執行配方。

測試類型

自動化測試有很多種類型,精確的定義通常因專案而異。在 Symfony 中,使用以下定義。如果您學到不同的東西,那不一定錯,只是與 Symfony 文件使用的不同。

單元測試
這些測試確保個別的原始碼單元 (例如,單一類別) 的行為符合預期。
整合測試
這些測試測試類別的組合,並且通常與 Symfony 的服務容器互動。這些測試尚未涵蓋完整運作的應用程式,這些稱為應用程式測試
應用程式測試
應用程式測試測試完整應用程式的行為。它們發出 HTTP 請求 (真實和模擬的請求),並測試回應是否符合預期。

單元測試

單元測試 確保個別的原始碼單元 (例如,單一類別或某些類別中的特定方法) 符合其設計並按預期運作。在 Symfony 應用程式中編寫單元測試與編寫標準 PHPUnit 單元測試沒有什麼不同。您可以在 PHPUnit 文件中了解它:為 PHPUnit 撰寫測試

依照慣例,tests/ 目錄應複製應用程式的目錄以進行單元測試。因此,如果您要測試 src/Form/ 目錄中的類別,請將測試放在 tests/Form/ 目錄中。透過 vendor/autoload.php 檔案 (預設在 phpunit.xml.dist 檔案中設定) 自動啟用自動載入。

您可以使用 bin/phpunit 命令執行測試

1
2
3
4
5
6
7
8
# run all tests of the application
$ php bin/phpunit

# run all tests in the Form/ directory
$ php bin/phpunit tests/Form

# run tests for the UserType class
$ php bin/phpunit tests/Form/UserTypeTest.php

提示

在大型測試套件中,為每種測試類型建立子目錄 ( tests/Unit/tests/Integration/tests/Application/ 等) 可能很有意義。

整合測試

與單元測試相比,整合測試將測試應用程式的較大部分 (例如,服務的組合)。整合測試可能想要使用 Symfony Kernel 從依賴注入容器中取得服務。

Symfony 提供了 KernelTestCase 類別,以協助您在測試中使用 bootKernel() 建立和啟動 kernel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tests/Service/NewsletterGeneratorTest.php
namespace App\Tests\Service;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class NewsletterGeneratorTest extends KernelTestCase
{
    public function testSomething(): void
    {
        self::bootKernel();

        // ...
    }
}

KernelTestCase 也確保您的 kernel 為每個測試重新啟動。這確保每個測試彼此獨立執行。

為了執行您的應用程式測試,KernelTestCase 類別需要找到應用程式 kernel 以進行初始化。kernel 類別通常在 KERNEL_CLASS 環境變數中定義 (包含在 Symfony Flex 提供的預設 .env.test 檔案中)

1
2
# .env.test
KERNEL_CLASS=App\Kernel

注意

如果您的使用案例更複雜,您也可以覆寫功能測試的 getKernelClass()createKernel() 方法,這會優先於 KERNEL_CLASS 環境變數。

設定您的測試環境

測試建立在 test 環境中執行的 kernel。這允許在 config/packages/test/ 中為您的測試設定特殊設定。

如果您安裝了 Symfony Flex,某些套件已經安裝了一些有用的測試設定。例如,預設情況下,Twig 套件設定為特別嚴格,以便在將程式碼部署到生產環境之前捕獲錯誤

1
2
3
# config/packages/test/twig.yaml
twig:
    strict_variables: true

您也可以完全使用不同的環境,或覆寫預設的偵錯模式 (true),方法是將每個模式作為選項傳遞給 bootKernel() 方法

1
2
3
4
self::bootKernel([
    'environment' => 'my_test_env',
    'debug'       => false,
]);

提示

建議在您的 CI 伺服器上將 debug 設定為 false 來執行測試,因為它可以顯著提高測試效能。這會停用清除快取。如果您的測試並非每次都在乾淨的環境中執行,您必須手動清除它,例如在 tests/bootstrap.php 中使用此程式碼

1
2
3
4
// ...

// ensure a fresh cache when debug mode is disabled
(new \Symfony\Component\Filesystem\Filesystem())->remove(__DIR__.'/../var/cache/test');

自訂環境變數

如果您需要為測試自訂某些環境變數 (例如 Doctrine 使用的 DATABASE_URL ),您可以透過覆寫 .env.test 檔案中所需的任何內容來完成

1
2
3
4
# .env.test

# ...
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=8.0.37"

在測試環境中,會讀取這些環境檔案 (如果變數在其中重複,則清單中較低的檔案會覆寫先前的項目)

  1. .env: 包含具有應用程式預設值的環境變數;
  2. .env.test: 覆寫/設定特定的測試值或變數;
  3. .env.test.local: 覆寫此機器的特定設定。

警告

在測試環境中不會使用 .env.local 檔案,以使每個測試設定盡可能一致。

在測試中檢索服務

在您的整合測試中,您通常需要從服務容器中取得服務,以呼叫特定方法。啟動 kernel 後,容器由 static::getContainer() 傳回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// tests/Service/NewsletterGeneratorTest.php
namespace App\Tests\Service;

use App\Service\NewsletterGenerator;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class NewsletterGeneratorTest extends KernelTestCase
{
    public function testSomething(): void
    {
        // (1) boot the Symfony kernel
        self::bootKernel();

        // (2) use static::getContainer() to access the service container
        $container = static::getContainer();

        // (3) run some service & test the result
        $newsletterGenerator = $container->get(NewsletterGenerator::class);
        $newsletter = $newsletterGenerator->generateMonthlyNews(/* ... */);

        $this->assertEquals('...', $newsletter->getContent());
    }
}

來自 static::getContainer() 的容器實際上是一個特殊的測試容器。它讓您可以存取公用服務和未移除的私有服務

注意

如果您需要測試已移除的私有服務 (那些未被任何其他服務使用的服務),您需要在 config/services_test.yaml 檔案中將這些私有服務宣告為公用。

模擬依賴性

有時模擬測試服務的依賴性可能很有用。從上一節的範例中,假設 NewsletterGenerator 依賴於指向私有 NewsRepository 服務的私有別名 NewsRepositoryInterface,並且您想要使用模擬的 NewsRepositoryInterface 而不是具體的那個

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ...
use App\Contracts\Repository\NewsRepositoryInterface;

class NewsletterGeneratorTest extends KernelTestCase
{
    public function testSomething(): void
    {
        // ... same bootstrap as the section above

        $newsRepository = $this->createMock(NewsRepositoryInterface::class);
        $newsRepository->expects(self::once())
            ->method('findNewsFromLastMonth')
            ->willReturn([
                new News('some news'),
                new News('some other news'),
            ])
        ;

        $container->set(NewsRepositoryInterface::class, $newsRepository);

        // will be injected the mocked repository
        $newsletterGenerator = $container->get(NewsletterGenerator::class);

        // ...
    }
}

不需要進一步設定,因為測試服務容器是一個特殊的容器,可讓您與私有服務和別名互動。

為測試設定資料庫

與資料庫互動的測試應使用自己的獨立資料庫,以免干擾其他組態環境中使用的資料庫。

若要執行此操作,請編輯或建立專案根目錄中的 .env.test.local 檔案,並為 DATABASE_URL 環境變數定義新值

1
2
# .env.test.local
DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=8.0.37"

這假設每個開發人員/機器都為測試使用不同的資料庫。如果每個機器上的測試設定都相同,請改用 .env.test 檔案並將其提交到共用儲存庫。深入了解在 Symfony 應用程式中使用多個 .env 檔案

之後,您可以使用以下命令建立測試資料庫和所有表格

1
2
3
4
5
# create the test database
$ php bin/console --env=test doctrine:database:create

# create the tables/columns in the test database
$ php bin/console --env=test doctrine:schema:create

提示

您可以在測試啟動程序期間執行這些命令以建立資料庫。

提示

常見的做法是在測試中將 _test 後綴附加到原始資料庫名稱。如果生產環境中的資料庫名稱稱為 project_acme,則測試資料庫的名稱可以是 project_acme_test

在每個測試之前自動重設資料庫

測試應彼此獨立,以避免副作用。例如,如果某些測試修改了資料庫 (透過新增或移除實體),則可能會變更其他測試的結果。

DAMADoctrineTestBundle 使用 Doctrine 交易,讓每個測試與未修改的資料庫互動。使用以下命令安裝它

1
$ composer require --dev dama/doctrine-test-bundle

現在,將其啟用為 PHPUnit 擴充功能

1
2
3
4
5
6
7
8
<!-- phpunit.xml.dist -->
<phpunit>
    <!-- ... -->

    <extensions>
        <extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
    </extensions>
</phpunit>

就這樣!此套件使用一個聰明的技巧:它在每個測試之前開始一個資料庫交易,並在測試完成後自動回滾它,以復原所有變更。請在 DAMADoctrineTestBundle 的文件中閱讀更多內容。

載入虛擬資料固定裝置

在測試資料庫中,通常使用虛假的或虛擬的資料,而不是來自生產資料庫的真實資料。這通常稱為「固定裝置資料」,而 Doctrine 提供了一個函式庫來建立和載入它們。使用以下命令安裝它

1
$ composer require --dev doctrine/doctrine-fixtures-bundle

然後,使用 SymfonyMakerBundlemake:fixtures 命令來產生一個空的固定裝置類別

1
2
3
4
$ php bin/console make:fixtures

The class name of the fixtures to create (e.g. AppFixtures):
> ProductFixture

然後,您修改並使用此類別將新實體載入資料庫。例如,若要將 Product 物件載入 Doctrine,請使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/DataFixtures/ProductFixture.php
namespace App\DataFixtures;

use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class ProductFixture extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        $product = new Product();
        $product->setName('Priceless widget');
        $product->setPrice(14.50);
        $product->setDescription('Ok, I guess it *does* have a price');
        $manager->persist($product);

        // add more products

        $manager->flush();
    }
}

清空資料庫並重新載入所有固定裝置類別,請使用

1
$ php bin/console --env=test doctrine:fixtures:load

如需更多資訊,請閱讀 DoctrineFixturesBundle 文件

應用程式測試

應用程式測試檢查應用程式所有不同層級 (從路由到視圖) 的整合。就 PHPUnit 而言,它們與單元測試或整合測試沒有什麼不同,但它們具有非常特定的工作流程

  1. 發出請求;
  2. 與頁面互動 (例如,點擊連結或提交表單);
  3. 測試回應;
  4. 沖洗並重複。

注意

本節中使用的工具可以透過 symfony/test-pack 安裝,如果您尚未安裝,請使用 composer require symfony/test-pack

撰寫您的第一個應用程式測試

應用程式測試是 PHP 檔案,通常位於應用程式的 tests/Controller/ 目錄中。它們通常擴充 WebTestCase。此類別在 KernelTestCase 之上新增了特殊邏輯。您可以在上面的整合測試章節中閱讀更多相關資訊。

如果您想要測試由 PostController 類別處理的頁面,請先使用 SymfonyMakerBundlemake:test 命令建立新的 PostControllerTest

1
2
3
4
5
6
7
$ php bin/console make:test

 Which test type would you like?:
 > WebTestCase

 The name of the test class (e.g. BlogPostTest):
 > Controller\PostControllerTest

這會建立以下測試類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// tests/Controller/PostControllerTest.php
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PostControllerTest extends WebTestCase
{
    public function testSomething(): void
    {
        // This calls KernelTestCase::bootKernel(), and creates a
        // "client" that is acting as the browser
        $client = static::createClient();

        // Request a specific page
        $crawler = $client->request('GET', '/');

        // Validate a successful response and some content
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Hello World');
    }
}

在上面的範例中,測試驗證 HTTP 回應是否成功,並且請求本文包含具有 "Hello world"<h1> 標籤。

request() 方法也會傳回一個爬蟲程式,您可以使用它在測試中建立更複雜的斷言 (例如,計算符合給定 CSS 選擇器的頁面元素數量)

1
2
$crawler = $client->request('GET', '/post/hello-world');
$this->assertCount(4, $crawler->filter('.comment'));

您可以在 DOM 爬蟲程式中深入了解爬蟲程式。

發出請求

測試用戶端模擬像瀏覽器一樣的 HTTP 用戶端,並向您的 Symfony 應用程式發出請求

1
$crawler = $client->request('GET', '/post/hello-world');

request() 方法接受 HTTP 方法和 URL 作為引數,並傳回 Crawler 實例。

提示

硬式編碼請求 URL 是應用程式測試的最佳實務。如果測試使用 Symfony 路由器產生 URL,它將無法偵測到對應用程式 URL 所做的任何變更,這可能會影響最終使用者。

request() 方法的完整簽名是

1
2
3
4
5
6
7
8
9
public function request(
    string $method,
    string $uri,
    array $parameters = [],
    array $files = [],
    array $server = [],
    ?string $content = null,
    bool $changeHistory = true
): Crawler

這可讓您建立您可以想到的所有類型的請求

提示

測試用戶端在 test 環境 (或啟用 framework.test 選項的任何位置) 中作為容器中的 test.client 服務提供。這表示如果需要,您可以完全覆寫該服務。

一個測試中的多個請求

在發出請求後,後續的請求將會使客戶端重新啟動核心。這會從頭開始重新建立容器,以確保請求是隔離的,並且每次都使用新的服務物件。這種行為可能會產生一些意想不到的後果:例如,安全令牌將被清除、Doctrine 實體將被分離等等。

首先,您可以呼叫客戶端的 disableReboot() 方法來重置核心,而不是重新啟動它。實際上,Symfony 將會呼叫每個標記為 kernel.reset 的服務的 reset() 方法。然而,這會清除安全令牌、分離 Doctrine 實體等等。

為了要解決這個問題,建立一個 編譯器通道(compiler pass),以從您的測試環境中的某些服務中移除 kernel.reset 標籤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Kernel.php
namespace App;

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel implements CompilerPassInterface
{
    use MicroKernelTrait;

    // ...

    public function process(ContainerBuilder $container): void
    {
        if ('test' === $this->environment) {
            // prevents the security token to be cleared
            $container->getDefinition('security.token_storage')->clearTag('kernel.reset');

            // prevents Doctrine entities to be detached
            $container->getDefinition('doctrine')->clearTag('kernel.reset');

            // ...
        }
    }
}

瀏覽網站

Client 支援許多可以在真實瀏覽器中執行的操作

1
2
3
4
5
6
$client->back();
$client->forward();
$client->reload();

// clears all cookies and the history
$client->restart();

注意

back()forward() 方法會跳過在請求 URL 時可能發生的重新導向,就像一般瀏覽器一樣。

重新導向

當請求返回重新導向回應時,client 不會自動跟隨它。您可以檢查回應,然後使用 followRedirect() 方法強制重新導向

1
$crawler = $client->followRedirect();

如果您希望 client 自動跟隨所有重新導向,您可以在執行請求之前呼叫 followRedirects() 方法來強制執行

1
$client->followRedirects();

如果您將 false 傳遞給 followRedirects() 方法,則將不再跟隨重新導向

1
$client->followRedirects(false);

使用者登入(身分驗證)

當您想要為受保護的頁面新增應用程式測試時,您必須先以使用者身分「登入」。重現實際步驟(例如提交登入表單)會使測試變得非常緩慢。因此,Symfony 提供了一個 loginUser() 方法來模擬您的功能測試中的登入。

建議不要使用真實使用者登入,而是僅為測試建立使用者。您可以使用 Doctrine 資料夾載入器(data fixtures),僅在測試資料庫中載入測試使用者。

在資料庫中載入使用者後,使用您的使用者儲存庫來獲取此使用者,並使用 $client->loginUser() 來模擬登入請求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// tests/Controller/ProfileControllerTest.php
namespace App\Tests\Controller;

use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ProfileControllerTest extends WebTestCase
{
    // ...

    public function testVisitingWhileLoggedIn(): void
    {
        $client = static::createClient();
        $userRepository = static::getContainer()->get(UserRepository::class);

        // retrieve the test user
        $testUser = $userRepository->findOneByEmail('john.doe@example.com');

        // simulate $testUser being logged in
        $client->loginUser($testUser);

        // test e.g. the profile page
        $client->request('GET', '/profile');
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Hello John!');
    }
}

您可以將任何 UserInterface 實例傳遞給 loginUser()。此方法會建立一個特殊的 TestBrowserToken 物件,並將其儲存在測試 client 的 session 中。如果您需要在這個令牌中定義自訂屬性,您可以使用 loginUser() 方法的 tokenAttributes 參數。

若要設定特定的防火牆(預設會設定為 main

1
$client->loginUser($testUser, 'my_firewall');

注意

根據設計,當使用無狀態防火牆時,loginUser() 方法無法運作。相反地,在每個 request() 呼叫中新增適當的令牌/標頭。

發出 AJAX 請求

client 提供了一個 xmlHttpRequest() 方法,該方法與 request() 方法具有相同的參數,並且是發出 AJAX 請求的快捷方式

1
2
// the required HTTP_X_REQUESTED_WITH header is added automatically
$client->xmlHttpRequest('POST', '/submit', ['name' => 'Fabien']);

傳送自訂標頭

如果您的應用程式根據某些 HTTP 標頭運作,請將它們作為 createClient() 的第二個參數傳遞

1
2
3
4
$client = static::createClient([], [
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
]);

您也可以在每次請求的基礎上覆寫 HTTP 標頭

1
2
3
4
$client->request('GET', '/', [], [], [
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
]);

警告

您的自訂標頭名稱必須遵循 RFC 3875 第 4.1.18 節 中定義的語法:將 - 替換為 _,將其轉換為大寫,並在結果前加上 HTTP_。例如,如果您的標頭名稱為 X-Session-Token,請傳遞 HTTP_X_SESSION_TOKEN

回報例外

在應用程式測試中偵錯例外可能很困難,因為預設情況下它們會被捕獲,您需要查看日誌才能查看拋出了哪個例外。在測試 client 中停用例外捕獲,允許 PHPUnit 回報例外

1
$client->catchExceptions(false);

存取內部物件

如果您使用 client 來測試您的應用程式,您可能想要存取 client 的內部物件

1
2
$history = $client->getHistory();
$cookieJar = $client->getCookieJar();

您也可以取得與最新請求相關的物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// the HttpKernel request instance
$request = $client->getRequest();

// the BrowserKit request instance
$request = $client->getInternalRequest();

// the HttpKernel response instance
$response = $client->getResponse();

// the BrowserKit response instance
$response = $client->getInternalResponse();

// the Crawler instance
$crawler = $client->getCrawler();

存取分析器資料

在每個請求上,您可以啟用 Symfony 分析器來收集有關該請求內部處理的資料。例如,分析器可用於驗證給定的頁面在載入時執行的資料庫查詢次數是否少於特定數量。

若要取得上次請求的分析器,請執行以下操作

1
2
3
4
5
6
7
// enables the profiler for the very next request
$client->enableProfiler();

$crawler = $client->request('GET', '/profiler');

// gets the profile
$profile = $client->getProfile();

有關在測試中使用分析器的具體詳細資訊,請參閱如何在功能測試中使用分析器文章。

與回應互動

就像真實的瀏覽器一樣,Client 和 Crawler 物件可用於與您收到的頁面互動

使用 clickLink() 方法點擊第一個包含給定文字的連結(或第一個具有該 alt 屬性的可點擊圖片)

1
2
3
4
$client = static::createClient();
$client->request('GET', '/post/hello-world');

$client->clickLink('Click here');

如果您需要存取 Link 物件,該物件提供特定於連結的有用方法(例如 getMethod()getUri()),請改用 Crawler::selectLink() 方法

1
2
3
4
5
6
7
8
$client = static::createClient();
$crawler = $client->request('GET', '/post/hello-world');

$link = $crawler->selectLink('Click here')->link();
// ...

// use click() if you want to click the selected link
$client->click($link);

提交表單

使用 submitForm() 方法提交包含給定按鈕的表單

1
2
3
4
5
6
$client = static::createClient();
$client->request('GET', '/post/hello-world');

$crawler = $client->submitForm('Add comment', [
    'comment_form[content]' => '...',
]);

submitForm() 的第一個參數是任何包含在表單中的 <button><input type="submit"> 的文字內容、idvaluename。第二個可選參數用於覆寫預設表單欄位值。

注意

請注意,您選擇的是表單按鈕而不是表單,因為一個表單可以有多個按鈕。如果您使用遍歷 API,請記住您必須尋找按鈕。

如果您需要存取 Form 物件,該物件提供特定於表單的有用方法(例如 getUri()getValues()getFiles()),請改用 Crawler::selectButton() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$client = static::createClient();
$crawler = $client->request('GET', '/post/hello-world');

// select the button
$buttonCrawlerNode = $crawler->selectButton('submit');

// retrieve the Form object for the form belonging to this button
$form = $buttonCrawlerNode->form();

// set values on a form object
$form['my_form[name]'] = 'Fabien';
$form['my_form[subject]'] = 'Symfony rocks!';

// submit the Form object
$client->submit($form);

// optionally, you can combine the last 2 steps by passing an array of
// field values while submitting the form:
$client->submit($form, [
    'my_form[name]'    => 'Fabien',
    'my_form[subject]' => 'Symfony rocks!',
]);

根據表單類型,您可以使用不同的方法來填寫輸入

1
2
3
4
5
6
7
8
9
10
11
12
// selects an option or a radio
$form['my_form[country]']->select('France');

// ticks a checkbox
$form['my_form[like_symfony]']->tick();

// uploads a file
$form['my_form[photo]']->upload('/path/to/lucas.jpg');

// In the case of a multiple file upload
$form['my_form[field][0]']->upload('/path/to/lucas.jpg');
$form['my_form[field][1]']->upload('/path/to/lisa.jpg');

提示

您可以不將表單名稱硬編碼為欄位名稱的一部分(例如,先前範例中的 my_form[...]),而是可以使用 getName() 方法來取得表單名稱。

提示

如果您有意想要選擇「無效」的 select/radio 值,請參閱 DomCrawler 元件

提示

您可以透過呼叫 Form 物件上的 getValues() 方法來取得將要提交的值。上傳的檔案在 getFiles() 返回的單獨陣列中可用。getPhpValues()getPhpFiles() 方法也會返回提交的值,但採用 PHP 格式(它使用方括號表示法轉換鍵 - 例如 my_form[subject] - 為 PHP 陣列)。

提示

submit()submitForm() 方法定義了可選參數,以在提交表單時新增自訂伺服器參數和 HTTP 標頭

1
2
$client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']);
$client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']);

測試回應 (斷言)

現在測試已造訪頁面並與其互動(例如填寫表單),現在是時候驗證是否顯示了預期的輸出。

由於所有測試都基於 PHPUnit,因此您可以在測試中使用任何 PHPUnit 斷言。結合測試 Client 和 Crawler,這讓您可以檢查任何您想要的東西。

但是,Symfony 為最常見的情況提供了有用的快捷方法

回應斷言

assertResponseIsSuccessful(string $message = '', bool $verbose = true)
斷言回應成功(HTTP 狀態碼為 2xx)。
assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true)
斷言特定的 HTTP 狀態碼。
assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true)
斷言回應是重新導向回應(可選地,您可以檢查目標位置和狀態碼)。例外的位置可以是絕對路徑或相對路徑。
assertResponseHasHeader(string $headerName, string $message = '')/assertResponseNotHasHeader(string $headerName, string $message = '')
斷言給定的標頭在回應中是(否)可用的,例如 assertResponseHasHeader('content-type');
assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = '')/assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = '')
斷言給定的標頭在回應中(否)包含預期的值,例如 assertResponseHeaderSame('content-type', 'application/octet-stream');
assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')/assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')
斷言給定的 Cookie 出現在回應中(可選地檢查特定的 Cookie 路徑或網域)。
assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '')
斷言給定的 Cookie 存在且設定為預期的值。
assertResponseFormatSame(?string $expectedFormat, string $message = '')
斷言 getFormat() 方法返回的回應格式與預期的值相同。
assertResponseIsUnprocessable(string $message = '', bool $verbose = true)
斷言回應無法處理(HTTP 狀態碼為 422)

7.1

$verbose 參數是在 Symfony 7.1 中引入的。

請求斷言

assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = '')
斷言給定的 請求屬性 設定為預期的值。
assertRouteSame($expectedRoute, array $parameters = [], string $message = '')
斷言請求符合給定的路由,並且可選地符合路由參數。

瀏覽器斷言

assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')/assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')
斷言測試 Client 是否(否)已設定給定的 Cookie(表示 Cookie 是由測試中的任何回應設定的)。
assertBrowserCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '')
斷言測試 Client 中的給定 Cookie 設定為預期的值。
assertThatForClient(Constraint $constraint, string $message = '')

在 Client 中斷言給定的 Constraint。適用於以與內建斷言相同的方式使用您的自訂斷言(即不將 Client 作為參數傳遞)

1
2
3
4
5
// add this method in some custom class imported in your tests
protected static function assertMyOwnCustomAssert(): void
{
    self::assertThatForClient(new SomeCustomConstraint());
}

Crawler 斷言

assertSelectorExists(string $selector, string $message = '')/assertSelectorNotExists(string $selector, string $message = '')
斷言給定的選擇器在回應中(否)匹配至少一個元素。
assertSelectorCount(int $expectedCount, string $selector, string $message = '')
斷言回應中存在預期數量的選擇器元素
assertSelectorTextContains(string $selector, string $text, string $message = '')/assertSelectorTextNotContains(string $selector, string $text, string $message = '')
斷言匹配給定選擇器的第一個元素(否)包含預期的文字。
assertAnySelectorTextContains(string $selector, string $text, string $message = '')/assertAnySelectorTextNotContains(string $selector, string $text, string $message = '')
斷言匹配給定選擇器的任何元素(否)包含預期的文字。
assertSelectorTextSame(string $selector, string $text, string $message = '')
斷言匹配給定選擇器的第一個元素的內容等於預期的文字。
assertAnySelectorTextSame(string $selector, string $text, string $message = '')
斷言匹配給定選擇器的任何元素等於預期的文字。
assertPageTitleSame(string $expectedTitle, string $message = '')
斷言 <title> 元素等於給定的標題。
assertPageTitleContains(string $expectedTitle, string $message = '')
斷言 <title> 元素包含給定的標題。
assertInputValueSame(string $fieldName, string $expectedValue, string $message = '')/assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = '')
斷言具有給定名稱的表單輸入的值(否)等於預期的值。
assertCheckboxChecked(string $fieldName, string $message = '')/assertCheckboxNotChecked(string $fieldName, string $message = '')
斷言具有給定名稱的核取方塊是(否)選取的。
assertFormValue(string $formSelector, string $fieldName, string $value, string $message = '')/assertNoFormValue(string $formSelector, string $fieldName, string $message = '')
斷言匹配給定選擇器的第一個表單的欄位值(否)等於預期的值。

郵件程式斷言

assertEmailCount(int $count, ?string $transport = null, string $message = '')
斷言已傳送預期數量的電子郵件。
assertQueuedEmailCount(int $count, ?string $transport = null, string $message = '')
斷言已排隊預期數量的電子郵件(例如,使用 Messenger 元件)。
assertEmailIsQueued(MessageEvent $event, string $message = '')/assertEmailIsNotQueued(MessageEvent $event, string $message = '')
斷言給定的郵件程式事件是(否)已排隊。使用 getMailerEvent(int $index = 0, ?string $transport = null) 按索引檢索郵件程式事件。
assertEmailAttachmentCount(RawMessage $email, int $count, string $message = '')
斷言給定的電子郵件具有預期數量的附件。使用 getMailerMessage(int $index = 0, ?string $transport = null) 按索引檢索特定的電子郵件。
assertEmailTextBodyContains(RawMessage $email, string $text, string $message = '')/assertEmailTextBodyNotContains(RawMessage $email, string $text, string $message = '')
斷言給定電子郵件的文字主體(否)包含預期的文字。
assertEmailHtmlBodyContains(RawMessage $email, string $text, string $message = '')/assertEmailHtmlBodyNotContains(RawMessage $email, string $text, string $message = '')
斷言給定電子郵件的 HTML 主體(否)包含預期的文字。
assertEmailHasHeader(RawMessage $email, string $headerName, string $message = '')/assertEmailNotHasHeader(RawMessage $email, string $headerName, string $message = '')
斷言給定的電子郵件(否)已設定預期的標頭。
assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')/assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')
斷言給定的電子郵件(否)已將預期的標頭設定為預期的值。
assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')
斷言給定的地址標頭等於預期的電子郵件地址。此斷言將地址(例如 Jane Smith <jane@example.com>)標準化為 jane@example.com
assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = '')/assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = '')
斷言給定電子郵件的主旨(否)包含預期的主旨。

通知器斷言

assertNotificationCount(int $count, ?string $transportName = null, string $message = '')
斷言已建立給定數量的通知(總計或針對給定的傳輸方式)。
assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = '')
斷言已排隊給定數量的通知(總計或針對給定的傳輸方式)。
assertNotificationIsQueued(MessageEvent $event, string $message = '')
斷言給定的通知已排隊。
assertNotificationIsNotQueued(MessageEvent $event, string $message = '')
斷言給定的通知未排隊。
assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = '')
斷言給定的文字包含在給定通知的主旨中。
assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = '')
斷言給定的文字未包含在給定通知的主旨中。
assertNotificationTransportIsEqual(MessageInterface $notification, string $transportName, string $message = '')
斷言給定通知的傳輸方式名稱與給定的文字相同。
assertNotificationTransportIsNotEqual(MessageInterface $notification, string $transportName, string $message = '')
斷言給定通知的傳輸方式名稱與給定的文字不相同。

HttpClient 斷言

提示

對於以下所有斷言,必須在觸發 HTTP 請求的程式碼之前呼叫 $client->enableProfiler()

assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client')
斷言已使用給定的 URL 呼叫,如果指定,則使用給定的方法主體和標頭。預設情況下,它將檢查 HttpClient,但您也可以傳遞特定的 HttpClient ID。(如果請求已呼叫多次,它將會成功。)
assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client')
斷言未使用 GET 或指定的方法呼叫給定的 URL。預設情況下,它將檢查 HttpClient,但可以指定 HttpClient ID。
assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client')
斷言已在 HttpClient 上發出給定數量的請求。預設情況下,它將檢查 HttpClient,但您也可以傳遞特定的 HttpClient ID。

端對端測試 (E2E)

如果您需要將應用程式作為一個整體進行測試,包括 JavaScript 程式碼,您可以使用真實的瀏覽器而不是測試 client。這稱為端對端測試,它是測試應用程式的絕佳方式。

這可以透過 Panther 元件實現。您可以在 專用頁面中了解更多資訊。

本作品,包括程式碼範例,均根據 Creative Commons BY-SA 3.0 授權條款授權。
TOC
    版本