helmich/phpunit-json-assert
Adds concise JSON assertions to PHPUnit using JSONPath expressions and JSON Schema validation. Use the JsonAssertions trait to verify complex JSON/data structures with readable assert* helpers. Install via Composer and choose the branch matching your PHPUnit/PHP version.
Installation:
composer require --dev helmich/phpunit-json-assert
Add the trait to your test class:
use Helmich\JsonAssert\JsonAssert;
class MyTest extends TestCase
{
use JsonAssert;
}
First Use Case: Verify a JSON response matches expected structure:
$actual = json_decode(file_get_contents('response.json'), true);
$expected = ['key' => 'value', 'nested' => ['array' => true]];
$this->assertJson($actual, $expected);
Where to Look First:
API Response Validation:
$response = $this->get('/api/users');
$this->assertJson($response->json(), [
'data' => [
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane']
],
'meta' => ['count' => 2]
]);
Dynamic Assertions with Data Providers:
public function providerTestCases()
{
return [
['valid' => true, 'expected' => ['status' => 'success']],
['valid' => false, 'expected' => ['status' => 'error']],
];
}
public function testDynamicJson($valid, $expected)
{
$this->assertJson($this->getJsonResponse($valid), $expected);
}
Partial Matching:
$this->assertJsonPartial([
'id' => 1,
'name' => 'John'
], $actual);
Schema-Like Validation:
$this->assertJsonSchema([
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string']
],
'required' => ['id', 'name']
], $actual);
Laravel HTTP Tests:
Combine with Laravel\Sanctum\Sanctum for authenticated API tests:
$response = $this->actingAs($user)->get('/api/profile');
$this->assertJson($response->json(), ['name' => 'Test User']);
Factories + JSON Asserts: Use factories to generate test data, then assert against expected JSON:
$user = User::factory()->create();
$this->assertJson($user->toArray(), [
'id' => $user->id,
'email_verified_at' => null
]);
Custom Matchers: Extend the trait for domain-specific assertions:
trait CustomJsonAsserts
{
protected function assertJsonHasPaginatedStructure(array $actual)
{
$this->assertJson($actual, [
'data' => $this->isArray(),
'meta' => [
'current_page' => $this->isInteger(),
'per_page' => $this->isInteger()
]
]);
}
}
Strict Typing:
1.0) will fail if compared to integers (1).assertJsonLoose() for lenient comparisons or normalize data:
$this->assertJsonLoose($actual, $expected);
Nested Arrays/Objects:
assertJsonArrayOrder() to ignore order:
$this->assertJsonArrayOrder(['id', 'name'], $actual['users']);
Null vs. Missing Keys:
null and missing keys are treated differently. Explicitly include null in expectations:
$this->assertJson(['key' => null], $actual); // Passes if key exists and is null
$this->assertJson(['key' => $this->isMissing()], $actual); // Passes if key is missing
Circular References:
JSON_ERROR_UTF8 or stack overflows.json_decode($json, true, 512, JSON_THROW_ON_ERROR) or limit depth in tests.Laravel Collections:
$this->assertJson($user->toArray(), ['name' => 'Test']);
Diff Output: Enable detailed diffs for failed assertions:
$this->assertJson($actual, $expected, 'Custom error message', true); // $showDiff = true
Pretty-Print JSON:
Use json_encode($actual, JSON_PRETTY_PRINT) to inspect complex structures.
Custom Assertion Messages:
$this->assertJson(
$actual,
$expected,
'Response must include `data` key with user details'
);
Custom Assertion Methods: Add methods to the trait for reusable logic:
protected function assertJsonHasRequiredFields(array $fields, array $actual)
{
foreach ($fields as $field) {
$this->assertArrayHasKey($field, $actual, "Missing required field: {$field}");
}
}
Override Default Behavior:
Extend the trait and override methods like assertJson():
trait ExtendedJsonAsserts
{
use JsonAssert;
protected function assertJson(array $actual, array $expected, string $message = '', bool $showDiff = false)
{
// Custom logic (e.g., trim whitespace from strings)
$actual = array_map(function ($value) {
return is_string($value) ? trim($value) : $value;
}, $actual);
parent::assertJson($actual, $expected, $message, $showDiff);
}
}
Integration with Laravel Exceptions: Throw custom exceptions for JSON validation failures:
try {
$this->assertJson($response->json(), $expected);
} catch (JsonAssertionFailedError $e) {
throw new \RuntimeException("API validation failed: " . $e->getMessage(), 0, $e);
}
Performance:
private $cachedJson;
protected function getJsonResponse(): array
{
return $this->cachedJson ??= json_decode($this->get('/api/large-data'), true);
}
How can I help you explore Laravel packages today?