Refactoring code snippet for test suite

Published 8 months ago by xtremer360

I'm trying to figure out how I can better refactor this section in my code because it is located in every test file in my test suite. The only difference is the the slug is different for the permission.

Any ideas?

public function setUp()
{
    parent::setUp();

    $this->user = factory(User::class)->create();
    $this->role = factory(Role::class)->create(['slug' => 'admin']);
    $this->permission = factory(Permission::class)->create(['slug' => 'slug-here']);

    $this->role->givePermissionTo($this->permission);
    $this->user->assignRole($this->role);
}
Best Answer (As Selected By xtremer360)
MikeHopley

You could add a method to your TestCase.php:

<?php
namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}

protected function setupData($slug)
{
    $this->user = factory(User::class)->create();
    $this->role = factory(Role::class)->create(['slug' => 'admin']);
    $this->permission = factory(Permission::class)->create(['slug' => $slug]);
    
    $this->role->givePermissionTo($this->permission);
    $this->user->assignRole($this->role);
}

Then in your tests:

public function setUp()
{
    parent::setUp();

    $this->setupData('choose-your-slug');
}
jdc1898

Can you use the __construct method and pass your slug?

MikeHopley

You could add a method to your TestCase.php:

<?php
namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}

protected function setupData($slug)
{
    $this->user = factory(User::class)->create();
    $this->role = factory(Role::class)->create(['slug' => 'admin']);
    $this->permission = factory(Permission::class)->create(['slug' => $slug]);
    
    $this->role->givePermissionTo($this->permission);
    $this->user->assignRole($this->role);
}

Then in your tests:

public function setUp()
{
    parent::setUp();

    $this->setupData('choose-your-slug');
}
xtremer360

@MikeHopley Thank you. That was my desired effect. What I'm trying to extend that to is if the tests I have set up in 95% of my tests that check to see if they have permission or not then they can or cannot perform the action and guests cannot perform the action.

How should I handle if they don't have the permission assigned to the role that user has? Currently, I am creating a generic second role and not assigning the permission to that user. Should that be moved to a method on the TestCase class as well since that is duplicated in many other tests?

As a reference, the term resource refers to ANY action I have on my system. I just used the resource as a generic name.

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\Role;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class AddResourceTest extends TestCase
{
    use DatabaseMigrations;

    public function setUp()
    {
        parent::setUp();

        $this->setupUser('create-resource');
    }

    /** @test */
    public function users_who_have_permission_can_view_the_add_resource_form()
    {
        $response = $this->actingAs($this->user)->get(route('resources.create'));

        $response->assertSuccessful();
        $response->assertViewHas('statuses');
    }

    /** @test */
    public function users_who_dont_have_permission_cannot_view_the_add_resource_form()
    {
        $userWithoutPermission = factory(User::class)->create();
        $role = factory(Role::class)->create(['name' => 'editor']);
        $userWithoutPermission->assignRole($role);

        $response = $this->actingAs($userWithoutPermission)->get(route('resources.create'));

        $response->assertStatus(403);
    }

    /** @test */
    public function guests_cannot_view_the_add_resource_form()
    {
        $response = $this->get(route('resources.create'));

        $response->assertStatus(302);
        $response->assertRedirect(route('login'));
    }
MikeHopley

Whenever you have repeated logic, you can refactor it to a reusable method. It's possible to put this directly on TestCase. However, you might find this makes your TestCase a little bloated.

Personally, I tend to limit TestCase methods to very generic things. For example, I have a followRedirects() method, which helps with HTTP testing.

Another option is to use traits. Put the methods in a trait, like /tests/ConfiguresResources.php, and then use the trait in your tests. The methods will be available on the test class.

When the setup is not duplicated (or not duplicated much) between tests, then you can simply put the method directly on the test class itself.

Ultimately, this is all just code reuse. Choose whatever makes sense to you, and seems easiest to understand.

Please sign in or create an account to participate in this conversation.