Install the Bundle:
composer require app-verk/api-test-cases
Ensure AppVerk\ApiTestCasesBundle\ApiTestCasesBundle is enabled in config/bundles.php.
Extend Base Test Case:
Create a test class extending JsonApiTestCase:
use AppVerk\ApiTestCasesBundle\Api\Cases\JsonApiTestCase;
class MyApiTest extends JsonApiTestCase
{
// Your test methods here
}
Configure Test Fixtures:
Use app-verk/alice-bundle for fixture loading (included as a dependency). Example fixture:
# config/packages/test/alice.yaml
alice:
fixtures:
- '%kernel.project_dir%/tests/fixtures/users.yml'
First Test Case:
public function testGetUser()
{
$this->loadFixtures(['users.yml']);
$response = $this->client->get('/api/users/1');
$this->assertResponse($response, 'users/success', Response::HTTP_OK);
}
src/Api/Cases/JsonApiTestCase.php: Core test case class with assertions.tests/fixtures/: Directory for Alice fixture files (e.g., users.yml).config/packages/test/: Test-specific configurations (e.g., alice.yaml, security.yaml).Define Expected Responses:
Create schema files (e.g., users/success.json) in tests/data/ to define expected API responses.
Example (users/success.json):
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
Load Fixtures: Use Alice fixtures to populate the database for tests:
protected function setUp(): void
{
$this->loadFixtures(['users.yml']);
parent::setUp();
}
Assert Responses:
public function testCreateUser()
{
$response = $this->client->post('/api/users', [
'json' => ['name' => 'Jane Doe', 'email' => 'jane@example.com']
]);
$this->assertResponse($response, 'users/success', Response::HTTP_CREATED);
}
Authentication:
Reuse the authenticateFixtureUser pattern for JWT/OAuth:
protected function authenticate()
{
$this->authenticateFixtureUser('users/admin.yml');
}
Symfony Mocker Container:
Use polishsymfonycommunity/symfony-mocker-container to mock services in tests:
$container = $this->createMockContainer();
$container->get('some.service')->method('doSomething')->willReturn(true);
Guzzle HTTP Client:
Leverage Guzzle for external API testing (included via guzzlehttp/guzzle):
$client = new Client(['base_uri' => 'http://api.example.com']);
$response = $client->get('/endpoint');
$this->assertResponse($response, 'external/success.json');
Diff Assertions:
Use phpspec/php-diff for detailed response comparisons:
$this->assertJsonStringEqualsJsonFile(
'tests/data/users/success.json',
$response->getContent()
);
Fixture Loading Order: Fixtures are loaded alphabetically. Explicitly define dependencies in fixture files:
# users.yml
AppBundle\Entity\User:
user1:
roles: ['ROLE_USER']
# ...
Response Schema Mismatches:
Ensure JSON schema files match the actual API response structure. Use php-diff for clear failure messages:
$this->assertResponseStructure($response, [
'id' => 'integer',
'name' => 'string',
'email' => 'string'
]);
Static Client State: Avoid test pollution by resetting the client between tests:
protected function tearDown(): void
{
self::$staticClient->setDefaultOption('headers', []);
parent::tearDown();
}
Symfony 5+ Compatibility:
The bundle targets Symfony 3 but may work with 4/5. Override createKernel() if needed:
protected function createKernel(array $options = []): KernelInterface
{
$kernel = parent::createKernel($options);
$kernel->boot();
return $kernel;
}
Enable API Debugging:
Add this to config/packages/test/debug.yaml:
framework:
router:
debug: '%kernel.debug%'
Log Failed Requests: Use Monolog to log failed assertions:
$this->logger->error('Failed assertion', [
'response' => $response->getContent(),
'expected' => file_get_contents('tests/data/users/success.json')
]);
Custom Assertions:
Extend JsonApiTestCase to add domain-specific assertions:
class CustomApiTest extends JsonApiTestCase
{
protected function assertTokenExpiry($response, $expiry)
{
$data = json_decode($response->getContent(), true);
$this->assertGreaterThan(time(), $data['exp'], 'Token expiry is valid');
}
}
Custom Response Validators:
Override validateResponse() to add logic:
protected function validateResponse(Response $response, $expected, $statusCode)
{
if ($statusCode === Response::HTTP_UNAUTHORIZED) {
$this->assertArrayHasKey('error', json_decode($response->getContent(), true));
}
parent::validateResponse($response, $expected, $statusCode);
}
Dynamic Fixture Loading: Use traits to centralize fixture logic:
trait FixtureLoader
{
protected function loadUserFixtures()
{
$this->loadFixtures(['users/base.yml', 'users/admin.yml']);
}
}
Parallel Testing:
Configure PHPUnit for parallel tests in phpunit.xml.dist:
<phpunit>
<extensions>
<extension class="Parallel\Extension"/>
</extensions>
</phpunit>
How can I help you explore Laravel packages today?