spatie/tabular-assertions
Write readable “tabular assertions” for Pest or PHPUnit by describing expected data as a Markdown-like table and comparing it to actual arrays/collections. Ideal for ordered datasets like time series, financials, or database rows, with clear diffs.
Installation:
composer require --dev spatie/tabular-assertions
For Pest users, no additional config is needed. For PHPUnit, ensure you have PHPUnit 9.5+.
First Use Case: Compare a collection of Eloquent models to a table definition:
test('order items match expected values', function () {
$order = Order::factory()->hasItems(3)->create();
expect($order->items)->toMatchTable('
| #id | #order_id | name | quantity |
| #1 | #1 | Pen | 2 |
| #2 | #1 | Paper | 1 |
| #3 | #1 | Pencil | 5 |
');
});
Where to Look First:
Use toMatchTable() to compare collections/arrays to a Markdown table:
expect($users)->toMatchTable('
| #id | #name | #email |
| #1 | John | john@example.com |
| #2 | Jane | jane@example.com |
');
# prefix skips column validation (useful for auto-increment IDs).# enforce exact matches.Validate computed or dynamic data:
expect($order->items)->toMatchTable(function ($item) {
return [
'name' => $item->name,
'total_price' => $item->price * $item->quantity,
];
}, '
| name | total_price |
| Pen | 10.00 |
| Paper | 5.00 |
');
toMatchTablePartial()Ignore order or missing rows:
expect($users)->toMatchTablePartial('
| #id | #name |
| #1 | John |
');
Use callbacks for complex logic:
expect($posts)->toMatchTable('
| #id | #title | #is_published |
| #1 | Hello | ' . (new \Spatie\TabularAssertions\ColumnMatcher\BooleanMatcher(true)) . ' |
');
Combine with Laravel factories for test data:
test('factory output matches table', function () {
$users = User::factory()->count(3)->create();
expect($users)->toMatchTable('
| #id | #name | #email |
| #1 | Alice | alice@example.com |
| #2 | Bob | bob@example.com |
');
});
Validate API responses (e.g., from Laravel HTTP tests):
$response = $this->getJson('/api/users');
expect($response->json('data'))->toMatchTable('
| #id | #name |
| #1 | Alice |
');
Extend functionality in Pest.php:
use Spatie\TabularAssertions\TabularAssertions;
expect()->extend('matchUserTable', function ($table) {
return TabularAssertions::assertMatchesTable($this->value, $table);
});
// Usage:
expect($users)->matchUserTable('| #id | #name |');
Laravel Eloquent:
with() to eager-load relationships before asserting:
$users = User::with('posts')->get();
expect($users)->toMatchTable('| #id | #name | posts_count |', [
'posts_count' => fn ($user) => $user->posts->count(),
]);
Pest vs. PHPUnit:
expect() syntax (cleaner, more expressive).assertMatchesTable() in assertThat():
$this->assertThat($users, \Spatie\TabularAssertions\TabularAssertions::matchesTable('| #id | #name |'));
CI/CD:
phpunit.xml or pest.php for consistent formatting:
<php>
<env name="TESTBENCH" value="true"/>
</php>
IDE Support:
@method in PHPDoc for autocompletion:
/**
* @method $this toMatchTable(string $table)
*/
class Expectation extends \Expectation {}
Whitespace Sensitivity:
| #id | name | #quantity | #price |
| #1 | Pen | 2 | 5.00 |
Column Order Matters:
expect($users)->toMatchTable(function ($user) {
return [
'email' => $user->email,
'name' => $user->name,
];
}, '| email | #name |');
Boolean/Null Values:
| #is_active |
| true | <!-- Not: | true | -->
null for nullable columns:
| #description |
| null |
Large Datasets:
toMatchTablePartial() or paginate data:
$users->forPage(1, 10)->toMatchTablePartial('| #id | #name |');
Time/Date Handling:
expect($events)->toMatchTable('
| #id | #created_at |
| #1 | 2023-01-01T00:00:00 |
');
Custom Column Matchers:
Failure Messages:
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
- | #id | #name |
- | #1 | Alice |
+ | #id | #name |
+ | #1 | alice |
trim() strings in closures).Logging Tables:
dd($users->map(fn ($u) => [$u->id, $u->name])->toArray());
PHPUnit vs. Pest:
assertMatchesTable() may not show diffs as clearly as Pest’s expect(). Use Pest for better debugging.Reusable Tables:
trait UserTable {
protected function userTable(): string {
return '
| #id | #name | #email |
| #1 | Alice | alice@example.com |
';
}
}
Parameterized Tests:
@data or PHPUnit’s @dataProvider:
test('users match table', function (array $users, string $table) {
expect($users)->toMatchTable($table);
})->with([
[User::factory()->create(), '
| #id | #name |
| #1 | Alice |
'],
]);
Performance:
$
How can I help you explore Laravel packages today?