draw/data-tester
Lightweight PHP data testing helper for validating arrays/objects against expected shapes and values. Useful for unit tests and quick assertions, with simple matchers and readable failure messages to spot mismatches fast.
Installation Add the package via Composer:
composer require --dev draw/data-tester
Requires PHPUnit (v5.7, 6.0, or 7.0) and Symfony PropertyAccess.
First Test Case
Create a test class extending DataTester (or use traits if preferred):
use Draw\DataTester\DataTester;
class UserTest extends DataTester
{
public function testUserData()
{
$user = new User(['name' => 'John', 'email' => 'john@example.com']);
$this->assert($user)
->hasProperty('name')
->isEqualTo('John')
->hasProperty('email')
->isEmail();
}
}
Key Entry Points
assert($data): Start fluent assertions on any data structure (objects, arrays, collections).Fluent Assertions for Data Validation Chain assertions for nested structures:
$this->assert($user)
->hasProperty('address')
->isArray()
->hasKey('city')
->isEqualTo('Paris');
Testing Collections Useful for Eloquent collections or arrays:
$this->assert($users)
->isIterable()
->hasCount(3)
->each(function ($user) {
$this->assert($user)->hasProperty('id');
});
Custom Assertions
Extend DataTester or use traits to add domain-specific checks:
trait AssertsUser
{
protected function assertUser($user)
{
return $this->assert($user)
->hasProperty('roles')
->isArray()
->hasCountBetween(1, 5);
}
}
Integration with Laravel
$this->app->singleton(DataTester::class, function ($app) {
return new DataTester($app['property_access']);
});
abstract class TestCase extends \Tests\TestCase
{
use DataTester;
}
Testing API Responses Validate JSON responses from controllers:
$response = $this->get('/api/users/1');
$this->assert(json_decode($response->getContent(), true))
->hasKey('data')
->hasKey('meta')
->hasProperty('data.id')
->isEqualTo(1);
Property Access Issues
__get() magic methods.->dump() to inspect data structures mid-assertion:
$this->assert($user)->dump()->hasProperty('name');
Deprecated PHPUnit Features
isEqualTo() over constraints.Performance with Large Data
each() over 10,000 items). Pre-filter data if needed.False Positives in isEmail()
isEmail() may not cover all edge cases (e.g., internationalized emails). Extend with a custom validator:
$this->assert($user)->hasProperty('email')->custom(function ($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
});
Reusable Assertion Groups Create methods for common patterns (e.g., validating API responses):
protected function assertApiSuccessResponse($response)
{
return $this->assert(json_decode($response->getContent(), true))
->hasKey('success', true)
->isEqualTo(true)
->hasKey('data');
}
Soft Assertions for Partial Validation
Use ->soft() to continue testing after a failure (useful for optional fields):
$this->assert($user)
->soft()
->hasProperty('middleName') // Fails silently
->hasProperty('name') // Continues
->isEqualTo('John');
Testing with Factories Combine with Laravel’s factories for consistent test data:
$user = User::factory()->create();
$this->assert($user)->hasProperty('created_at')->isInstanceOf(DateTime::class);
Custom Error Messages Override default messages for clarity:
$this->assert($user)
->hasProperty('name', 'User must have a name')
->isEqualTo('John', 'Name must be "John"');
Legacy Code Workarounds
__call() in your test class:
public function __call($method, $args)
{
if (str_starts_with($method, 'assertPrivate')) {
$property = substr($method, 14);
return $this->assertProperty($this->getObject(), $property, ...$args);
}
}
How can I help you explore Laravel packages today?