WebTestCase
Функциональные тесты проверяют поведение приложения через HTTP-запросы. WebTestCase предоставляет HTTP-клиент для симуляции запросов.
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class HomePageTest extends WebTestCase
{
public function testHomepageReturnsSuccessfulResponse(): void
{
$client = static::createClient();
$client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(200);
}
public function testHomepageContainsTitle(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$this->assertSelectorTextContains('h1', 'Welcome');
$this->assertSelectorExists('.hero-section');
}
}
Client Object
Создание клиента
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class ApiTest extends WebTestCase
{
public function testCreateClient(): void
{
// Basic client
$client = static::createClient();
// Client with server parameters
$client = static::createClient([], [
'HTTP_HOST' => 'example.com',
'HTTP_ACCEPT' => 'application/json',
]);
// With environment/debug options
$client = static::createClient([
'environment' => 'test',
'debug' => true,
]);
$client->request('GET', '/');
$this->assertResponseIsSuccessful();
}
}
HTTP-методы
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class ApiEndpointTest extends WebTestCase
{
public function testGetRequest(): void
{
$client = static::createClient();
$client->request('GET', '/api/users');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/json');
}
public function testPostRequest(): void
{
$client = static::createClient();
$client->request('POST', '/api/users', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'email' => '[email protected]',
'name' => 'Test User',
]));
$this->assertResponseStatusCodeSame(201);
}
public function testJsonRequest(): void
{
$client = static::createClient();
// Shortcut for JSON requests
$client->jsonRequest('POST', '/api/users', [
'email' => '[email protected]',
'name' => 'Test User',
]);
$this->assertResponseStatusCodeSame(201);
$response = $client->getResponse();
$data = json_decode($response->getContent(), true);
$this->assertArrayHasKey('id', $data);
$this->assertSame('[email protected]', $data['email']);
}
public function testDeleteRequest(): void
{
$client = static::createClient();
$client->request('DELETE', '/api/users/1');
$this->assertResponseStatusCodeSame(204);
}
}
Симуляция запросов
Параметры request()
<?php
declare(strict_types=1);
// Full signature:
$crawler = $client->request(
method: 'POST',
uri: '/submit',
parameters: ['name' => 'John'], // POST/GET parameters
files: [], // Uploaded files
server: [ // Server variables
'HTTP_ACCEPT_LANGUAGE' => 'en',
'HTTP_AUTHORIZATION' => 'Bearer token123',
'CONTENT_TYPE' => 'application/json',
],
content: null, // Raw body content
changeHistory: true, // Add to browser history
);
Аутентификация в тестах
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class SecuredAreaTest extends WebTestCase
{
public function testAdminDashboardRequiresAuth(): void
{
$client = static::createClient();
$client->request('GET', '/admin');
$this->assertResponseRedirects('/login');
}
public function testAdminDashboardAccessibleForAdmin(): void
{
$client = static::createClient();
// Login as user (requires UserRepository fixture)
$userRepository = static::getContainer()
->get('doctrine')
->getRepository(User::class);
$user = $userRepository->findOneBy(['email' => '[email protected]']);
$client->loginUser($user);
$client->request('GET', '/admin');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Dashboard');
}
public function testApiWithToken(): void
{
$client = static::createClient();
$client->request('GET', '/api/profile', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer valid-token-here',
]);
$this->assertResponseIsSuccessful();
}
}
Работа с формами
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class RegistrationTest extends WebTestCase
{
public function testUserCanRegister(): void
{
$client = static::createClient();
// Load the page with the form
$crawler = $client->request('GET', '/register');
$this->assertResponseIsSuccessful();
// Find the form and fill it
$form = $crawler->selectButton('Register')->form([
'registration_form[email]' => '[email protected]',
'registration_form[password][first]' => 'SecurePass123!',
'registration_form[password][second]' => 'SecurePass123!',
'registration_form[agreeTerms]' => true,
]);
// Submit the form
$client->submit($form);
// Assert redirect after successful registration
$this->assertResponseRedirects('/login');
// Follow the redirect
$client->followRedirect();
$this->assertSelectorTextContains('.flash-success', 'Registration successful');
}
public function testRegistrationValidation(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/register');
// Submit with invalid data
$form = $crawler->selectButton('Register')->form([
'registration_form[email]' => 'invalid-email',
'registration_form[password][first]' => '123',
'registration_form[password][second]' => '456',
]);
$client->submit($form);
// Should stay on same page with errors
$this->assertResponseIsUnprocessable(); // 422
$this->assertSelectorExists('.form-error-message');
}
}
Работа с формой по шагам
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class ContactFormTest extends WebTestCase
{
public function testContactFormSubmission(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/contact');
// Method 1: Select form by button text
$form = $crawler->selectButton('Send Message')->form();
// Set individual fields
$form['contact[name]'] = 'John Doe';
$form['contact[email]'] = '[email protected]';
$form['contact[message]'] = 'Hello, this is a test message';
// Set select field
$form['contact[subject]']->select('support');
// Set checkbox
$form['contact[newsletter]']->tick();
// Submit
$client->submit($form);
$this->assertResponseRedirects('/contact/thanks');
}
}
Redirect Following
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class RedirectTest extends WebTestCase
{
public function testLoginRedirectsToHomepage(): void
{
$client = static::createClient();
// Submit login form...
$client->request('POST', '/login', [
'email' => '[email protected]',
'password' => 'password',
]);
// Assert redirect
$this->assertResponseRedirects('/');
// Follow the redirect
$crawler = $client->followRedirect();
// Now on the homepage
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('nav', 'Logout');
}
public function testMaxRedirectFollowing(): void
{
$client = static::createClient();
$client->setMaxRedirects(5);
// Follow redirects automatically
$client->followRedirects(true);
$client->request('GET', '/old-page');
// Already followed all redirects
$this->assertResponseIsSuccessful();
}
}
Assertions для ответов
<?php
declare(strict_types=1);
// Response assertions
$this->assertResponseIsSuccessful(); // 2xx
$this->assertResponseStatusCodeSame(200); // Exact code
$this->assertResponseRedirects('/path'); // 3xx + location
$this->assertResponseRedirects('/path', 302); // Exact redirect
$this->assertResponseHeaderSame('content-type', 'application/json');
$this->assertResponseHasHeader('X-Custom-Header');
$this->assertResponseNotHasHeader('X-Debug');
$this->assertResponseHasCookie('session');
$this->assertResponseFormatSame('json');
// Selector assertions (DOM)
$this->assertSelectorExists('h1');
$this->assertSelectorNotExists('.error');
$this->assertSelectorTextContains('h1', 'Welcome');
$this->assertSelectorTextSame('.title', 'Exact Title');
$this->assertSelectorCount(5, '.product-card');
// Email assertions
$this->assertEmailCount(1);
$this->assertQueuedEmailCount(1);
Доступ к контейнеру в тестах
<?php
declare(strict_types=1);
namespace App\Tests\Functional;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class UserTest extends WebTestCase
{
public function testUserCreatedInDatabase(): void
{
$client = static::createClient();
// Submit registration form...
$client->request('POST', '/register', [/* ... */]);
// Access the container to verify database state
$container = static::getContainer();
$userRepository = $container->get(UserRepository::class);
$user = $userRepository->findOneBy(['email' => '[email protected]']);
$this->assertNotNull($user);
$this->assertSame('[email protected]', $user->getEmail());
}
}
KernelTestCase для интеграционных тестов
<?php
declare(strict_types=1);
namespace App\Tests\Integration;
use App\Service\OrderProcessor;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
final class OrderProcessorTest extends KernelTestCase
{
public function testProcessOrder(): void
{
self::bootKernel();
$processor = static::getContainer()->get(OrderProcessor::class);
$result = $processor->process(orderId: 42);
$this->assertTrue($result->isSuccessful());
}
}
Итоги
WebTestCase-- функциональные тесты с HTTP-клиентомcreateClient()-- создание тестового клиента$client->request()-- симуляция HTTP-запроса$client->loginUser()-- аутентификация в тестахselectButton()->form()-- заполнение и отправка формfollowRedirect()-- следование за редиректамиassertResponseIsSuccessful(),assertSelectorTextContains()и др.KernelTestCase-- тесты с контейнером (без HTTP)