Write functional tests for Symfony controllers and HTTP endpoints using WebTestCase, BrowserKit, and test clients
/plugin marketplace add MakFly/superpowers-symfony/plugin install makfly-superpowers-symfony@MakFly/superpowers-symfonyThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Functional tests verify that your application works correctly from HTTP request to response.
composer require --dev symfony/test-pack
composer require --dev zenstruck/foundry
<?php
// tests/Functional/Controller/HomeControllerTest.php
namespace App\Tests\Functional\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomeControllerTest extends WebTestCase
{
public function testHomePageLoads(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Welcome');
}
}
// GET request
$client->request('GET', '/users');
// POST with JSON
$client->request('POST', '/api/users', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['email' => 'test@example.com']));
// PUT
$client->request('PUT', '/api/users/1', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['name' => 'Updated']));
// DELETE
$client->request('DELETE', '/api/users/1');
// With headers
$client->request('GET', '/api/users', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer token123',
'HTTP_ACCEPT' => 'application/json',
]);
// Status codes
$this->assertResponseIsSuccessful(); // 2xx
$this->assertResponseStatusCodeSame(201);
$this->assertResponseRedirects('/login');
// Content
$this->assertSelectorExists('form.login');
$this->assertSelectorTextContains('h1', 'Welcome');
$this->assertSelectorCount(5, '.user-card');
// JSON
$response = json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('id', $response);
// Headers
$this->assertResponseHeaderSame('Content-Type', 'application/json');
use App\Tests\Factory\UserFactory;
public function testProtectedEndpoint(): void
{
$client = static::createClient();
$user = UserFactory::createOne()->object();
$client->loginUser($user);
$client->request('GET', '/dashboard');
$this->assertResponseIsSuccessful();
}
public function testAdminOnlyEndpoint(): void
{
$client = static::createClient();
// Regular user - should fail
$user = UserFactory::createOne(['roles' => ['ROLE_USER']])->object();
$client->loginUser($user);
$client->request('GET', '/admin');
$this->assertResponseStatusCodeSame(403);
// Admin user - should succeed
$admin = UserFactory::createOne(['roles' => ['ROLE_ADMIN']])->object();
$client->loginUser($admin);
$client->request('GET', '/admin');
$this->assertResponseIsSuccessful();
}
public function testSubmitsContactForm(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/contact');
// Select form and fill fields
$form = $crawler->selectButton('Send')->form([
'contact[name]' => 'John Doe',
'contact[email]' => 'john@example.com',
'contact[message]' => 'Hello there!',
]);
$client->submit($form);
$this->assertResponseRedirects('/contact/thanks');
}
public function testFormValidation(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/contact');
$form = $crawler->selectButton('Send')->form([
'contact[email]' => 'invalid-email',
]);
$crawler = $client->submit($form);
$this->assertSelectorExists('.invalid-feedback');
}
public function testPageContent(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/users');
// Count elements
$this->assertCount(10, $crawler->filter('.user-card'));
// Get text
$title = $crawler->filter('h1')->text();
$this->assertEquals('User List', $title);
// Get attribute
$link = $crawler->filter('a.profile-link')->attr('href');
$this->assertEquals('/users/1', $link);
// Follow link
$link = $crawler->selectLink('View Profile')->link();
$client->click($link);
$this->assertRouteSame('user_show');
}
<?php
namespace App\Tests\Functional\Api;
use App\Tests\Factory\UserFactory;
use App\Tests\Factory\ProductFactory;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
class ProductApiTest extends WebTestCase
{
use Factories;
use ResetDatabase;
private $client;
protected function setUp(): void
{
$this->client = static::createClient();
}
public function testListsProducts(): void
{
ProductFactory::createMany(5);
$this->client->request('GET', '/api/products');
$this->assertResponseIsSuccessful();
$data = json_decode($this->client->getResponse()->getContent(), true);
$this->assertCount(5, $data['hydra:member']);
}
public function testCreatesProduct(): void
{
$admin = UserFactory::createOne(['roles' => ['ROLE_ADMIN']])->object();
$this->client->loginUser($admin);
$this->client->request('POST', '/api/products', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'name' => 'New Product',
'price' => 1999,
]));
$this->assertResponseStatusCodeSame(201);
$data = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals('New Product', $data['name']);
}
public function testValidatesProduct(): void
{
$admin = UserFactory::createOne(['roles' => ['ROLE_ADMIN']])->object();
$this->client->loginUser($admin);
$this->client->request('POST', '/api/products', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'name' => '', // Invalid: empty name
]));
$this->assertResponseStatusCodeSame(422);
}
}
use Symfony\Bundle\FrameworkBundle\Test\MailerAssertionsTrait;
class RegistrationTest extends WebTestCase
{
use MailerAssertionsTrait;
public function testSendsWelcomeEmail(): void
{
$client = static::createClient();
$client->request('POST', '/register', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'email' => 'new@example.com',
'password' => 'password123',
]));
$this->assertEmailCount(1);
$email = $this->getMailerMessage();
$this->assertEmailHtmlBodyContains($email, 'Welcome');
$this->assertEmailAddressContains($email, 'to', 'new@example.com');
}
}
use Zenstruck\Foundry\Test\ResetDatabase;
class OrderTest extends WebTestCase
{
use ResetDatabase; // Resets database before each test
public function testCreatesOrder(): void
{
// Database is clean here
$user = UserFactory::createOne();
// ...
}
}
ResetDatabase for isolationApplies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.