skip to Main Content

I’m trying to make a pest test file easier to read.

Currently, I’ve got some standard tests:

test('can get subscribers latest subscription', function () {
     $this->seed(PlansTestSeeder::class);
    $this->seed(SubscriptionsTestSeeder::class);

    $this->assertDatabaseCount('plans', 2);
    $this->assertDatabaseCount('subscriptions', 0);


    Subscription::factory()->create([
        "plan_id" => Plan::where("slug", "bronze")->first()->id
    ]);
    Subscription::factory()->create([
        "plan_id" => Plan::where("slug", "silver")->first()->id
    ]);
    Subscription::factory()->create([
        "plan_id" => Plan::where("slug", "silver")->first()->id,
        "status"  => "expired"
    ]);
    Subscription::factory()->trashed()->create();

    $this->assertDatabaseCount('subscriptions', 4);

});

test('can get subscribers active subscriptions', function () {
    $this->seed(PlansTestSeeder::class);
    $this->seed(SubscriptionsTestSeeder::class);

    $silverPlan = Plan::where("slug", "silver")->first();

    $subscription1 = Subscription::factory()->create([
        "plan_id"         => Plan::where("slug", "silver")->first()->id,
        "subscriber_id"   => 1,
        "subscriber_type" => "ApresourcingFrameworkBillingTestsModelsSubscriber",
        "created_at"      => now()->subDays(2),
        "started_at"      => now()->subDays(2)
    ]);
    $subscription2 = Subscription::factory()->create([
        "plan_id"         => $silverPlan->id,
        "subscriber_id"   => 1,
        "subscriber_type" => "ApresourcingFrameworkBillingTestsModelsSubscriber",
        "created_at"      => now()->subDays(1),
        "started_at"      => now()->subDays(1)
    ]);

    $user         = Subscriber::find(1);
    $subscription = $user->latestSubscription();
    expect($subscription->id)->toBe($subscription2->id);
});

But to then remind myself what tests I’ve written, I’ve got to scroll up and down the page over and over again.

What I’d like to do is change to something like the following:

test('can get subscribers latest subscription', getLatestSubscription());
test('can get subscribers active subscriptions', getActiveSubscriptions());

function getLatestSubscription() {
    /// function code here
});

function getActiveSubscriptions() {
    // function code here
});

However, the test functions include references to $this, which is available within the normal closure, but it’s not available in the standard function as I’ve set it up here.

Edit: I’m using the laravel pest plugin – I’m not sure if that makes a difference to the use of $this

Is there any way to get around this?

3

Answers


  1. Chosen as BEST ANSWER

    Got there thanks to some hints in the repliers. Not as tidy as I would have liked, but at least it means all the test('description of test') calls are in one place at the bottom of the php file.

    
    $createSubscription = function () {
    
        $this->seed(PlansTestSeeder::class);
        $this->seed(SubscriptionsTestSeeder::class);
    
        $this->assertDatabaseCount('plans', 2);
        $this->assertDatabaseCount('subscriptions', 0);
    
    
        Subscription::factory()->create([
            "plan_id" => Plan::where("slug", "bronze")->first()->id
        ]);
        Subscription::factory()->create([
            "plan_id" => Plan::where("slug", "silver")->first()->id
        ]);
        Subscription::factory()->create([
            "plan_id" => Plan::where("slug", "silver")->first()->id,
            "status"  => "expired"
        ]);
        Subscription::factory()->trashed()->create();
    
        $this->assertDatabaseCount('subscriptions', 4);
    
    };
    
    $createBronzeSubscription = function () {
        $this->seed(PlansTestSeeder::class);
        $this->seed(SubscriptionsTestSeeder::class);
    
        Subscription::factory()->create([
            "plan_id" => Plan::where("slug", "bronze")->first()->id
        ]);
    
        $this->assertDatabaseCount('subscriptions', 1);
    };
    
    
    test('can create subscription', function () use ($createSubscription) {
        return Closure::bind(Closure::fromCallable($createSubscription), $this, get_class($this))($this);
    });
    
    test('can create bronze subscription', function () use ($createBronzeSubscription) {
        return Closure::bind(Closure::fromCallable($createBronzeSubscription), $this, get_class($this))($this);
    });
    

  2. Method 1: just call your function from inside a closure:

    function getActiveSubscriptions($obj) {
      // use $obj instead of $this
      $obj->doSomething();
    }
    
    
    test('can get subscribers latest subscription', 
       function () { 
            getLatestSubscription($this); 
       }
    );
    

    Method2: rewrite your test function for it to accept function name instead of closure. E.g.:

      function test($text, $fun) {
        print $text;
        print $fun();
      }
    
      function getLatestSubscription() {
        return "123";
      }
    
      test('can get subscribers latest subscription', 'getLatestSubscription');
    
    Login or Signup to reply.
  3. Practice

    Consider this, which modifies your spun-out functions to each return an anonymous function, and satisfies the call site you’d like to see:

    function getLatestSubscription()
    {
        return function () {
            // function code here
        };  
    };
    
    function getActiveSubscriptions()
    {
        return function () {
            // function code here
        };  
    };
    
    test('can get subscribers latest subscription', getLatestSubscription());
    test('can get subscribers active subscriptions', getActiveSubscriptions());
    

    If you’re comfortable with having the functions as closure variables at the same scope, you can remove one level of function invocation with the following:

    $getLatestSubscription = function () {
        // function code here
    };
    
    $getActiveSubscriptions = function () {
        // function code here
    };
    
    test('can get subscribers latest subscription', $getLatestSubscription);
    test('can get subscribers active subscriptions', $getActiveSubscriptions);
    

    Theory

    As the Pest test function accepts a Closure for its second argument, and binds that closure to the test case internally, any of the following would behave the same:

    test('closure literal', function () {
        $this->helperMethod();
        expect(true)->toBeTrue();
    });
    
    $closureVariable = function () {
        $this->helperMethod();
        expect(true)->toBeTrue();
    };
    test('closure variable', $closureVariable);
    
    function closureFactory()
    {
        return function () {
            $this->helperMethod();
            expect(true)->toBeTrue();
        };  
    }
    test('closure factory', closureFactory());
    

    For your use case the last is likely overkill, but could be useful in situations where you have test cases that differ in just a few ways.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search