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

Comparator Laravel Package

sebastian/comparator

sebastian/comparator compares PHP values for equality with type-aware comparators. Use the Factory to select the right comparator and get helpful ComparisonFailure details when assertions fail—ideal for test suites and tooling.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package as a dev dependency:

    composer require --dev sebastianbergmann/comparator:^8.2
    
  2. Basic usage in tests (PHPUnit/Pest):

    use SebastianBergmann\Comparator\Factory;
    use SebastianBergmann\Comparator\ComparisonFailure;
    
    $factory = new Factory();
    $comparator = $factory->getComparatorFor($actual, $expected);
    
    try {
        $comparator->assertEquals($actual, $expected);
        // Pass
    } catch (ComparisonFailure $e) {
        // Fail with detailed diff (now cleaner for canonicalized lists)
        $this->fail($e->getMessage());
    }
    
  3. Laravel-specific quick win: Replace assertSame() for Eloquent models/collections:

    $user = User::find(1);
    $expected = ['id' => 1, 'name' => 'John'];
    $comparator->assertEquals($user->toArray(), $expected);
    

First Use Case: API Response Testing

use Illuminate\Testing\TestResponse;

public function test_api_response()
{
    $response = $this->getJson('/api/users');
    $actual = $response->json();
    $expected = ['data' => [['id' => 1, 'name' => 'John']]];

    $factory = new Factory();
    $comparator = $factory->getComparatorFor($actual, $expected, Factory::CANONICALIZE);

    $comparator->assertEquals($actual, $expected);
    // Note: Diff output is now cleaner for canonicalized lists (e.g., sorted arrays)
}

Implementation Patterns

Core Workflows

1. Factory-Based Comparator Selection

Leverage the Factory to automatically select the right comparator for your data type:

$factory = new Factory();
$comparator = $factory->getComparatorFor($actual, $expected);
// Handles DateTime, arrays, objects, and now **closures by value** (v8.2.1)

2. Canonicalized Array Comparison (Improved)

For order-agnostic testing (e.g., API responses, Eloquent collections):

$comparator = $factory->getComparatorFor($actual, $expected, Factory::CANONICALIZE);
// Fixed in v8.2.1: String keys are now preserved during canonicalization

3. Closure Comparison (New in v8.2.1)

Compare closures by value (previously compared by reference):

$closure1 = fn() => 'test';
$closure2 = fn() => 'test';

$comparator = $factory->getComparatorFor($closure1, $closure2);
$comparator->assertEquals($closure1, $closure2); // Now works!

4. Diff Output Customization

Configure diff context lines for cleaner test failures:

$factory = new Factory();
$factory->setDiffContextLines(3); // Default is 3
// Note: Canonicalized list diffs are now more readable (v8.2.1)

Laravel-Specific Patterns

Testing Eloquent Models

public function test_user_creation()
{
    $user = User::create(['name' => 'John']);
    $expected = ['id' => 1, 'name' => 'John'];

    $comparator = (new Factory())->getComparatorFor($user->toArray(), $expected, Factory::CANONICALIZE);
    $comparator->assertEquals($user->toArray(), $expected);
    // String keys in arrays are preserved during canonicalization (v8.2.1)
}

API Response Testing with Collections

public function test_paginated_response()
{
    $response = $this->getJson('/api/users?page=1');
    $actual = $response->json()['data'];

    $comparator = (new Factory())->getComparatorFor($actual, $expected, Factory::CANONICALIZE);
    $comparator->assertEquals($actual, $expected);
    // Diff output is now cleaner for sorted/unsorted array comparisons
}

Database Seeder Validation

public function test_seeder_output()
{
    Artisan::call('db:seed');
    $actual = DB::table('users')->get()->toArray();
    $expected = [['id' => 1, 'name' => 'Admin']];

    $comparator = (new Factory())->getComparatorFor($actual, $expected, Factory::CANONICALIZE);
    $comparator->assertEquals($actual, $expected);
}

Carbon/DateTime Comparisons

public function test_scheduled_job_timezone()
{
    $actual = now();
    $expected = Carbon::parse('2023-01-01 12:00:00');

    $comparator = (new Factory())->getComparatorFor($actual, $expected);
    $comparator->assertEquals($actual, $expected);
}

Closure-Based Comparisons (New)

public function test_closure_behavior()
{
    $closure1 = fn($x) => $x * 2;
    $closure2 = fn($x) => $x * 2;

    $comparator = (new Factory())->getComparatorFor($closure1, $closure2);
    $comparator->assertEquals($closure1, $closure2); // Now compares by value
}

Gotchas and Tips

Pitfalls

  1. String Keys in Canonicalized Arrays (Fixed in v8.2.1)

    • Old Issue: Canonicalization would destroy string keys in associative arrays.
    • Fix: Now preserved. No action needed unless you were relying on broken behavior.
  2. Closure Comparison (New in v8.2.1)

    • Problem: Closures are now compared by value, which may reveal subtle differences.
    • Fix: Ensure closures are truly equivalent if comparing them:
      $closure1 = fn() => 'test';
      $closure2 = fn() => 'test';
      // These will now pass comparison
      
  3. Non-Deterministic Sorting in Mixed Arrays

    • Problem: Arrays with mixed types (e.g., [1, 'a', new stdClass()]) may still sort unpredictably.
    • Fix: Avoid canonicalization for mixed-type arrays or use Factory::NORMALIZE.
  4. Timezone-Sensitive DateTime Comparisons

    • Problem: DateTime objects may fail if timezones differ.
    • Fix: Normalize timezones before comparison:
      $actual->setTimezone(new DateTimeZone('UTC'));
      $expected->setTimezone(new DateTimeZone('UTC'));
      
  5. Performance Overhead

    • Problem: Deep comparisons (e.g., large nested arrays) may slow down tests.
    • Fix: Use Factory::NORMALIZE instead of CANONICALIZE for performance-critical paths.

Debugging Tips

  1. Inspect Diff Output (Improved in v8.2.1) Use the getDiff() method to debug failures (now cleaner for canonicalized lists):

    try {
        $comparator->assertEquals($actual, $expected);
    } catch (ComparisonFailure $e) {
        $this->fail($e->getDiff());
        // Diffs for canonicalized arrays are now more readable
    }
    
  2. Log Comparison Details For complex objects, log the actual/expected values before comparison:

    $this->info('Actual:', $actual);
    $this->info('Expected:', $expected);
    
  3. Test Canonicalization Separately Verify canonicalization works as expected (string keys are preserved):

    $comparator = (new Factory())->getComparatorFor($actual, $expected, Factory::CANONICALIZE);
    $this->assertTrue($comparator->compare($actual, $expected));
    
  4. Handle Edge Cases Explicitly For INF, -INF, or NaN:

    if (is_infinite($actual) || is_nan($actual)) {
        $this->fail("Actual value is INF/NaN");
    }
    

Extension Points

  1. Custom Comparators Implement SebastianBergmann\Comparator\Comparator for Laravel-specific types:

    class CollectionComparator implements Comparator
    {
        public function assertEquals($expected, $actual, $description = '', $delta = 0, $canonicalize = false, $ignoreCase = false)
        {
            if ($canonicalize) {
                $actual = $actual->sortBy(fn($item) => $item['id'])->values();
                $expected = Collection::make($expected)->sortBy(fn($item) => $item['id'])->values();
            }
            parent::assertEquals($expected, $actual, $description, $delta, $canonicalize, $ignoreCase);
        }
    }
    
  2. Override Factory Behavior Extend SebastianBergmann\Comparator\Factory to modify comparator selection:

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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope