Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tabular Assertions Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require --dev spatie/tabular-assertions
    

    For Pest users, no additional config is needed. For PHPUnit, ensure you have PHPUnit 9.5+.

  2. 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        |
        ');
    });
    
  3. Where to Look First:

    • README for syntax and examples.
    • Tests for real-world usage patterns.
    • Changelog for breaking changes.

Implementation Patterns

Core Workflows

1. Basic Table Assertions

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).
  • Columns without # enforce exact matches.

2. Dynamic Data with Closures

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        |
');

3. Partial Matches with toMatchTablePartial()

Ignore order or missing rows:

expect($users)->toMatchTablePartial('
    | #id | #name |
    |  #1 | John  |
');

4. Custom Column Matchers

Use callbacks for complex logic:

expect($posts)->toMatchTable('
    | #id | #title | #is_published |
    |  #1 | Hello  | ' . (new \Spatie\TabularAssertions\ColumnMatcher\BooleanMatcher(true)) . ' |
');

5. Integration with Factories

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      |
    ');
});

6. API Response Testing

Validate API responses (e.g., from Laravel HTTP tests):

$response = $this->getJson('/api/users');
expect($response->json('data'))->toMatchTable('
    | #id | #name |
    |  #1 | Alice |
');

7. Custom Assertion Macros (Pest)

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 |');

Integration Tips

  1. Laravel Eloquent:

    • Use 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(),
      ]);
      
  2. Pest vs. PHPUnit:

    • Pest: Use expect() syntax (cleaner, more expressive).
    • PHPUnit: Use assertMatchesTable() in assertThat():
      $this->assertThat($users, \Spatie\TabularAssertions\TabularAssertions::matchesTable('| #id | #name |'));
      
  3. CI/CD:

    • Add to phpunit.xml or pest.php for consistent formatting:
      <php>
          <env name="TESTBENCH" value="true"/>
      </php>
      
  4. IDE Support:

    • Use @method in PHPDoc for autocompletion:
      /**
       * @method $this toMatchTable(string $table)
       */
      class Expectation extends \Expectation {}
      

Gotchas and Tips

Pitfalls

  1. Whitespace Sensitivity:

    • Tables must align perfectly. Use a Markdown table generator (e.g., Tables Generator) to avoid manual errors.
    • Example of failing table (misaligned):
      | #id | name  |  #quantity |  #price |
      |  #1 | Pen   | 2          | 5.00   |
      
  2. Column Order Matters:

    • The table’s column order must match the collection’s keys/accessors. Reorder columns in the table or use a closure to transform data:
      expect($users)->toMatchTable(function ($user) {
          return [
              'email' => $user->email,
              'name' => $user->name,
          ];
      }, '| email               | #name |');
      
  3. Boolean/Null Values:

    • Explicitly cast booleans to avoid ambiguity:
      | #is_active |
      | true       |  <!-- Not: | true | -->
      
    • Use null for nullable columns:
      | #description |
      | null        |
      
  4. Large Datasets:

    • Avoid asserting thousands of rows. Use toMatchTablePartial() or paginate data:
      $users->forPage(1, 10)->toMatchTablePartial('| #id | #name |');
      
  5. Time/Date Handling:

    • Dates must match exactly. Use carbon instances or format consistently:
      expect($events)->toMatchTable('
          | #id | #created_at          |
          |  #1 | 2023-01-01T00:00:00   |
      ');
      
  6. Custom Column Matchers:

    • Overly complex matchers may slow tests. Prefer simple closures or table transformations.

Debugging

  1. Failure Messages:

    • The package provides detailed diffs. Example:
      Failed asserting that two arrays are equal.
      --- Expected
      +++ Actual
      @@ @@
       - | #id | #name |
       - |  #1 | Alice |
       + | #id | #name |
       + |  #1 | alice |
      
    • Fix: Ensure case sensitivity matches (e.g., trim() strings in closures).
  2. Logging Tables:

    • Temporarily log the actual data to debug:
      dd($users->map(fn ($u) => [$u->id, $u->name])->toArray());
      
  3. PHPUnit vs. Pest:

    • PHPUnit’s assertMatchesTable() may not show diffs as clearly as Pest’s expect(). Use Pest for better debugging.

Tips

  1. Reusable Tables:

    • Store tables in a separate file or trait for DRY tests:
      trait UserTable {
          protected function userTable(): string {
              return '
                  | #id | #name | #email               |
                  |  #1 | Alice | alice@example.com    |
              ';
          }
      }
      
  2. Parameterized Tests:

    • Combine with Pest’s @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 |
          '],
      ]);
      
  3. Performance:

    • Cache factory data if tests run slowly:
      $
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport