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

Black Box Laravel Package

innmind/black-box

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup in Laravel

  1. Installation

    composer require innmind/black-box
    

    Add to composer.json under require-dev if only for testing:

    "dev-require": {
        "innmind/black-box": "^1.0"
    }
    
  2. First Test File Create a test file (e.g., tests/Feature/PropertyTest.php):

    use Innmind\BlackBox\{Application, Set, Runner\Assert, Prove};
    use Tests\TestCase;
    
    class PropertyTest extends TestCase
    {
        public function testAddCommutativity()
        {
            Application::new([])
                ->tryToProve(static function(Prove $prove) {
                    yield $prove
                        ->proof('add is commutative')
                        ->given(Set::integers())
                        ->test(static fn(Assert $assert, int $a, int $b) =>
                            $assert->same($a + $b, $b + $a)
                        );
                });
        }
    }
    
  3. Run Tests

    php artisan test
    

    Or for a single test:

    php artisan test tests/Feature/PropertyTest.php
    

First Use Case: Validating Business Logic

Use BlackBox to verify invariants in your Laravel application, such as:

  • Order totals: Ensure discounts are applied correctly across multiple items.
  • User permissions: Validate that role-based access rules hold for edge cases.
  • API responses: Check that pagination or sorting logic behaves consistently.

Example: Testing a discount calculation:

$prove
    ->proof('discounts are applied correctly')
    ->given(Set::floats(0, 100), Set::floats(0, 1)) // (price, discountRate)
    ->test(static fn(Assert $assert, float $price, float $discountRate) =>
        $assert->same(
            applyDiscount($price, $discountRate),
            $price * (1 - $discountRate)
        )
    );

Implementation Patterns

1. Generating Test Data

Leverage Set to generate diverse inputs for testing:

  • Primitive types:
    Set::integers(1, 100)       // Random integers between 1-100
    Set::floats(0, 100)        // Random floats
    Set::strings(10)            // Random strings of length 10
    
  • Custom objects:
    Set::of(static fn() => User::factory()->make())
    
  • Collections:
    Set::of(static fn() => collect([1, 2, 3]))
    

2. Combining Proofs

Group related properties in a single test:

Application::new([])
    ->tryToProve(static function(Prove $prove) {
        yield $prove
            ->proof('user email is valid')
            ->given(Set::strings(5, 50))
            ->test(static fn(Assert $assert, string $email) =>
                $assert->true(Filter::validate($email))
            );

        yield $prove
            ->proof('user email is unique')
            ->given(Set::of(static fn() => User::factory()->make()))
            ->test(static fn(Assert $assert, User $user) =>
                $assert->false(User::where('email', $user->email)->exists())
            );
    });

3. Integrating with Laravel Factories

Use Set::of() with Laravel’s factories to test domain logic:

use App\Models\User;

$prove
    ->proof('user roles are enforced')
    ->given(Set::of(static fn() => User::factory()->state([
        'role' => Set::elements(['admin', 'editor', 'viewer'])
    ])))
    ->test(static fn(Assert $assert, User $user) =>
        $assert->same(
            canAccessDashboard($user),
            $user->role === 'admin'
        )
    );

4. Custom Shrinkers for Debugging

Override default shrinkers (e.g., for complex objects) to improve failure debugging:

use Innmind\BlackBox\Shrinker\Shrinker;

$customShrinker = new class implements Shrinker {
    public function shrink(mixed $value): iterable { /* ... */ }
};

Application::new([], [$customShrinker])
    ->tryToProve(...);

5. Parallel Testing

Run proofs in parallel for faster execution (Laravel 9+):

use Innmind\BlackBox\Runner\Parallel;

Application::new([], [], new Parallel(4)) // 4 parallel workers
    ->tryToProve(...);

Gotchas and Tips

Pitfalls

  1. Stateful Tests BlackBox is designed for stateless properties. Avoid testing:

    • Database transactions (use Laravel’s DatabaseTransactions trait instead).
    • External API calls (mock them with Laravel’s Mockery or Pest).

    Anti-pattern:

    // Fails: Assumes a clean DB state.
    $prove->test(static fn(Assert $assert) =>
        $assert->same(User::count(), 0)
    );
    
  2. Overly Complex Generators Generators with side effects (e.g., Set::of(static fn() => tap(new User(), fn($u) => $u->save()))) can bloat tests. Prefer pure functions.

  3. Ignoring Shrinking Always review shrunk values when a test fails—they reveal the minimal input that broke your property.

  4. Performance with Large Sets Limit generator size for slow operations:

    Set::integers(1, 1000)->size(100) // Test only 100 samples
    

Debugging Tips

  1. Inspect Generated Values Log inputs during failures:

    ->test(static fn(Assert $assert, int $a, int $b) => {
        logger()->debug("Failed with: a=$a, b=$b");
        $assert->same($a + $b, $b + $a);
    })
    
  2. Custom Assertions Extend Assert for domain-specific checks:

    $assert->custom('is_valid_order', static fn(Order $order) =>
        $order->total > 0 && $order->items->count() > 0
    );
    
  3. Focused Testing Use Application::new([], [], null, ['proof_name']) to run a single proof:

    Application::new([], [], null, ['add is commutative'])
        ->tryToProve(...);
    

Extension Points

  1. Custom Runners Implement Runner\RunnerInterface to integrate with Laravel’s test events:

    use Innmind\BlackBox\Runner\RunnerInterface;
    
    class LaravelRunner implements RunnerInterface {
        public function run(Proof $proof): void {
            // Dispatch Laravel events (e.g., testingStarted, testingFinished)
        }
    }
    
  2. Plugin System Extend Application with plugins for Laravel-specific features:

    Application::new([], [], null, [], [
        new class {
            public function __invoke(Application $app) {
                $app->setDatabaseTransactions(true);
            }
        }
    ]);
    
  3. Type-Safe Generators Use PHP 8.1+ attributes to validate generators:

    #[Assert\All(Assert\Type::class, 'int')]
    Set::integers();
    

Laravel-Specific Quirks

  1. Service Container Binding Bind BlackBox components to Laravel’s container for DI:

    $this->app->bind(Prove::class, static fn() => new Prove());
    
  2. Artisan Integration Create a custom Artisan command for ad-hoc property testing:

    php artisan blackbox:test --proof="add is associative"
    
  3. Pest Plugin Integrate with Pest for a more fluent syntax:

    use Innmind\BlackBox\{Application, Set};
    
    test('discounts are applied', function () {
        Application::new([])
            ->tryToProve(static function(Prove $prove) {
                yield $prove->proof('discount logic')
                    ->given(Set::floats(0, 100))
                    ->test(static fn(Assert $assert, float $price) =>
                        $assert->greaterThan(applyDiscount($price, 0.1), 0)
                    );
            });
    });
    
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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky