lchrusciel/api-test-case
PHPUnit TestCase for Symfony API development. Extends WebTestCase with easy JSON/XML response matching (via php-matcher) and optional Alice/Doctrine fixtures loading. Supports a clear TDD workflow using expected response files and helpful diffs.
Installation:
composer require --dev lchrusciel/api-test-case
No additional configuration is required for basic usage.
First Test Case:
Create a test class extending JsonApiTestCase (for JSON APIs) or XmlApiTestCase (for XML APIs):
namespace App\Tests\Controller;
use ApiTestCase\JsonApiTestCase;
class UserTest extends JsonApiTestCase
{
public function testGetUser()
{
$this->client->request('GET', '/api/users/1');
$this->assertResponse($this->client->getResponse(), 'user_response');
}
}
Expected Response File:
Create a file at tests/Responses/Expected/user_response.json:
{
"id": "@integer@",
"name": "@string@",
"email": "user@example.com"
}
Run Tests:
vendor/bin/phpunit
assertResponse() to validate JSON/XML responses against expected files.@string@, @integer@, etc.) for dynamic values like IDs or timestamps.Test-Driven API Development:
assertResponse() pointing to a non-existent response file.Response Validation:
// Basic assertion
$this->assertResponse($response, 'file_name');
// With custom HTTP status code
$this->assertResponse($response, 'file_name', 201);
Fixtures with Alice:
tests/DataFixtures/ORM/books.yml:
App\Entity\Book:
book1:
title: "Test Book"
author: "Test Author"
public function testWithFixtures()
{
$this->loadFixturesFromFile('books.yml');
$this->client->request('GET', '/api/books');
$this->assertResponse($this->client->getResponse(), 'books_list');
}
Custom Assertions:
{
"id": "@uuid@",
"created_at": "@string@.isDateTime()"
}
{
"data": [
{
"id": "@integer@",
"attributes": {
"name": "@string@"
}
}
]
}
Symfony Kernel:
Configure KERNEL_CLASS in phpunit.xml if using a custom kernel:
<php>
<server name="KERNEL_CLASS" value="App\Kernel" />
</php>
Database Transactions:
Enable Doctrine transactions in setUp() for isolated tests:
protected function setUp(): void
{
parent::setUp();
$this->enableDoctrineTransactions();
}
Authentication:
Use Symfony’s client->loginUser() or custom headers:
$this->client->request('POST', '/api/login', [
'json' => ['email' => 'test@example.com', 'password' => 'password']
]);
Environment-Specific Config:
Override EXPECTED_RESPONSE_DIR or FIXTURES_DIR in phpunit.xml for different environments.
File Paths:
Expected response files are in the correct directory (default: ../Responses relative to the test class).phpunit.xml or verify relative paths:
<server name="EXPECTED_RESPONSE_DIR" value="%kernel.project_dir%/tests/Responses/Expected/" />
Doctrine Fixtures:
tests/DataFixtures/ORM/ (default) or specify FIXTURES_DIR.config/bundles.php (Symfony 4+):
Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['test' => true],
Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['test' => true],
PHP-Matcher Patterns:
@string@ are case-sensitive and must match exactly.{
"status": "@string@.equals('active')"
}
HTTP Status Codes:
assertResponse() defaults to 200. Omit the status code to use the default.$this->assertResponse($response, 'file_name', 404);
Doctrine Transactions:
enableDoctrineTransactions() can lead to shared state between tests.setUp():
$this->enableDoctrineTransactions();
JSON Escaping:
ESCAPE_JSON is deprecated (default: false). Avoid relying on it for backward compatibility.Detailed Errors:
Use OPEN_ERROR_IN_BROWSER in phpunit.xml to open errors in a browser:
<server name="OPEN_ERROR_IN_BROWSER" value="true" />
<server name="OPEN_BROWSER_COMMAND" value="open %s" />
Response Dumping: Log raw responses for debugging:
file_put_contents(
sys_get_temp_dir() . '/debug_response.json',
$this->client->getResponse()->getContent()
);
Fixture Loading: Verify fixtures are loaded by checking the database or adding debug logs:
$this->loadFixturesFromFile('books.yml');
$this->assertDatabaseHas('book', ['title' => 'Test Book']);
Custom Matchers: Extend PHP-Matcher by adding custom assertions to expected files:
{
"email": "@string@.matches('/^[^\s@]+@[^\s@]+\.[^\s@]+$/')"
}
Pre/Post-Test Hooks:
Override setUp() or tearDown() for setup/teardown logic:
protected function setUp(): void
{
parent::setUp();
$this->client->request('POST', '/api/auth', ['json' => ['token' => 'test']]);
}
Custom Assertions: Create helper methods in your test class:
protected function assertResponseContains($response, $key, $value)
{
$data = json_decode($response->getContent(), true);
$this->assertArrayHasKey($key, $data);
$this->assertEquals($value, $data[$key]);
}
Symfony Events: Dispatch events in tests to trigger custom logic:
$this->client->getContainer()->get('event_dispatcher')->dispatch(
new SomeEvent(),
'some.event'
);
Temporary Directory:
Set TMP_DIR in phpunit.xml to control log file storage:
<server name="TMP_DIR" value="%kernel.project_dir%/var/log/tests/" />
Doctrine Support:
Disable with IS_DOCTRINE_ORM_SUPPORTED=false if not using Doctrine:
<server name="IS_DOCTRINE_ORM_SUPPORTED" value="false" />
PHPUnit 9+:
Data providers may need adjustments due to BC breaks. Use dataProvider() with explicit types:
public function testWithDataProvider()
{
$this->assertResponse($this->client->getResponse(), 'file_name');
}
public function dataProvider()
{
return [
['test1'],
['test2'],
];
}
How can I help you explore Laravel packages today?