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

Specify Laravel Package

codeception/specify

BDD-style specifications for PHP tests, built as a lightweight extension for Codeception. Adds a clean “specify” syntax to structure examples and expectations, making tests easier to read, write, and maintain across unit and functional suites.

Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require --dev codeception/specify
    

    Add to composer.json under require-dev if not using autoloading.

  2. Basic Usage Import the trait in your test class:

    use Codeception\Specify;
    
    class MyTest extends \Codeception\Test\Unit
    {
        use Specify;
    }
    
  3. First Use Case Replace traditional assert blocks with specify():

    public function testUserCreation()
    {
        $user = User::create(['name' => 'John']);
    
        specify('the user has a valid name', function () use ($user) {
            $user->name->shouldBe('John');
        });
    
        specify('the user is saved', function () use ($user) {
            User::where('id', $user->id)->shouldHaveCount(1);
        });
    }
    

Where to Look First


Implementation Patterns

Workflows

  1. Descriptive Test Blocks Replace verbose assert chains with readable specify() blocks:

    public function testOrderProcessing()
    {
        $order = Order::create(['status' => 'pending']);
    
        specify('order starts as pending', function () use ($order) {
            $order->status->shouldBe('pending');
        });
    
        $order->markAsPaid();
        specify('order updates to paid', function () use ($order) {
            $order->refresh()->status->shouldBe('paid');
        });
    }
    
  2. Data-Driven Specifications Use specify() with loops or factories for dynamic tests:

    public function testUserRoles()
    {
        $roles = ['admin', 'editor', 'guest'];
    
        foreach ($roles as $role) {
            specify("a $role has correct permissions", function () use ($role) {
                $user = User::factory()->create(['role' => $role]);
                $user->can($role)->shouldBeTrue();
            });
        }
    }
    
  3. Integration with Codeception Works seamlessly with Codeception’s actors (e.g., ActorTestCase):

    class UserCest
    {
        use Specify;
    
        public function _before(Actor $I)
        {
            $I->wantTo('test user workflow');
        }
    
        public function testLogin(Actor $I)
        {
            $I->amOnPage('/login');
            $I->fillField('email', 'user@example.com');
            $I->fillField('password', 'password');
            $I->click('Login');
    
            specify('user is redirected to dashboard', function () use ($I) {
                $I->seeCurrentUrlEquals('/dashboard');
            });
        }
    }
    
  4. Combining with PHPUnit Mix specify() with PHPUnit’s @dataProvider:

    public function testEdgeCases()
    {
        $this->specifyDataProvider('edgeCaseProvider', function ($input, $expected) {
            $result = process($input);
            $result->shouldBe($expected);
        });
    }
    
    public function edgeCaseProvider()
    {
        return [
            ['', null],
            [null, null],
            ['invalid', false],
        ];
    }
    

Integration Tips

  • Laravel Testing: Pair with Laravel\LaravelTestCase for HTTP tests:
    use Laravel\LaravelTestCase;
    use Codeception\Specify;
    
    class FeatureTest extends LaravelTestCase
    {
        use Specify;
    }
    
  • Custom Assertions: Extend Specify to add domain-specific specs:
    trait CustomSpecify
    {
        public function specifyPaymentIsValid($payment)
        {
            specify('payment amount is positive', function () use ($payment) {
                $payment->amount->shouldBeGreaterThan(0);
            });
            specify('payment has valid currency', function () use ($payment) {
                $payment->currency->shouldBeIn(['USD', 'EUR']);
            });
        }
    }
    
  • Test Isolation: Use specify() to isolate assertions in large test methods, improving readability and maintainability.

Gotchas and Tips

Pitfalls

  1. Scope Leaks Avoid referencing outer variables in specify() without use:

    // ❌ Fails (undefined $user in closure)
    specify('user exists', function () {
        $user->id->shouldBe(1); // Error!
    });
    
    // ✅ Correct
    specify('user exists', function () use ($user) {
        $user->id->shouldBe(1);
    });
    
  2. Overusing specify()

    • Anti-pattern: Breaking every assertion into a specify() block can lead to noise.
    • Rule of thumb: Use for logical groups of assertions (e.g., "given X, then Y and Z").
  3. Codeception-Specific Quirks

    • If using ActorTestCase, ensure specify() closures have access to the actor ($I) via use ($I).
    • Some Codeception modules (e.g., Db, Rest) may require chaining with specify() carefully to avoid state pollution.
  4. Performance

    • specify() adds minimal overhead, but avoid nesting it deeply (e.g., 100+ blocks in one test).
    • Prefer fewer, well-named specify() blocks over many trivial ones.

Debugging

  1. Failed Specifications

    • Errors in specify() closures show up as PHPUnit failures with the block’s description in the message.
    • Example output:
      1) Tests\UserTest::testCreation
      specify 'the user is saved' failed: User with id [1] not found.
      
  2. Variable Scope Issues

    • Use var_dump or dd() inside the closure to inspect variables:
      specify('debug user', function () use ($user) {
          var_dump($user->toArray());
      });
      
  3. Integration with Laravel Debugbar

    • If using barryvdh/laravel-debugbar, specify() blocks appear in the "Tests" tab with their descriptions.

Tips

  1. Naming Conventions

    • Use imperative mood for specify() descriptions:
      • ✅ "shows error message"
      • ❌ "should show error message" (redundant with shouldBe*)
  2. Grouping with @group

    • Combine with PHPUnit’s @group for CI filtering:
      /**
       * @group regression
       */
      public function testRegressionCases()
      {
          specify('...');
      }
      
  3. Dynamic Specifications

    • Generate specify() blocks dynamically for data-heavy tests:
      $testCases = [
          ['input' => 1, 'expected' => 1],
          ['input' => 2, 'expected' => 4],
      ];
      
      foreach ($testCases as $case) {
          specify("squares {$case['input']} to {$case['expected']}", function () use ($case) {
              square($case['input'])->shouldBe($case['expected']);
          });
      }
      
  4. Extending Functionality

    • Add custom specify* methods to the trait:
      trait ExtendedSpecify
      {
          public function specifyModelExists($model)
          {
              specify('model exists in database', function () use ($model) {
                  $model::where('id', $model->id)->shouldHaveCount(1);
              });
          }
      }
      
    • Merge traits in your test class:
      use Codeception\Specify;
      use ExtendedSpecify;
      
  5. Legacy Code

    • Gradually migrate from assert* to specify() without rewriting all tests at once.
    • Use both in the same test for clarity during transition:
      public function testHybrid()
      {
          assertTrue(true); // Legacy
          specify('new spec style', function () {
              true->shouldBeTrue();
          });
      }
      
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
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
twbs/bootstrap4