Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Functional Tests With WebTestCase

Functional Tests With WebTestCase

~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Unit tests from chapter 42 check ISOLATED logic – now let's test the INTERPLAY: a REAL HTTP request to /login, running through routing, security, AND the controller.

The WebTestCase base class

tests/Controller/SecurityControllerTest.php
<?php

declare(strict_types=1);

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class SecurityControllerTest extends WebTestCase
{
    public function testLoginPageLoads(): void
    {
        $client = static::createClient();
        $client->request('GET', '/login');

        self::assertResponseIsSuccessful();
        self::assertSelectorExists('form');
    }
}

createClient() creates a simulated HTTP client that tests AGAINST an ACTUALLY booted Symfony application (in the test environment from chapter 4) – request() fires a REAL request that runs through routing (chapter 7), security (block 5), AND the controller.

Important assertions for functional tests

  • assertResponseIsSuccessful() – a 2xx status code.
  • assertResponseStatusCodeSame(404) – a SPECIFIC status code.
  • assertResponseRedirects('/login') – redirects to a specific route.
  • assertSelectorExists('form') – a CSS selector finds an element in the rendered HTML.
  • assertSelectorTextContains('h1', 'Log In') – an element contains specific text.

Filling out and submitting a form

public function testRegistrationWorks(): void
{
    $client = static::createClient();
    $crawler = $client->request('GET', '/register');

    $client->submitForm('Register', [
        'registration_form[name]' => 'Test User',
        'registration_form[email]' => 'test@example.com',
        'registration_form[plainPassword]' => 'secure-password-123',
    ]);

    self::assertResponseRedirects('/login');
}

submitForm('Register', [...]) finds the button with the text "Register" (translated from the form's submit button), fills in the given field names (in Symfony Forms format from chapter 15: form_name[field_name]), and clicks it – SIMULATING real user behavior, INCLUDING the CSRF token built in chapter 18, which is AUTOMATICALLY included in the form and submitted along with it.

Simulating a logged-in user

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;

public function testProtectedPageRequiresLogin(): void
{
    $client = static::createClient();

    // Without login: redirected to the login page
    $client->request('GET', '/projects');
    self::assertResponseRedirects('/login');

    // With a simulated login: successful access
    $entityManager = static::getContainer()->get(EntityManagerInterface::class);
    $user = $entityManager->getRepository(User::class)->findOneBy(['email' => 'anna@example.com']);

    $client->loginUser($user);
    $client->request('GET', '/projects');
    self::assertResponseIsSuccessful();
}

loginUser() bypasses the ACTUAL login form process and authenticates the test client DIRECTLY – faster than filling out the login form every time, when it's NOT the login process itself being tested, but some OTHER, protected page.

static::getContainer(): accessing services in tests

getContainer() gives access to the SAME service container from block 6 – handy in tests, e.g. to directly create test data via EntityManagerInterface, instead of clicking through the entire registration workflow.

Tipp: Rule of thumb for block 7: unit tests (chapter 42) for ISOLATED business logic, functional tests (this chapter) for CRITICAL user workflows (login, registration, access control) – a HEALTHY project typically has CONSIDERABLY more unit tests than functional tests, since the latter are slower.